import { describe, it, expect, vi, beforeEach } from "vitest"; import { createAppContext, destroyAppContext } from "../app-context.js"; import type { AppConfig } from "../config.js"; // Mock dependencies vi.mock("@atbb/db", () => ({ createDb: vi.fn(() => ({})), })); vi.mock("../firehose.js", () => ({ FirehoseService: vi.fn(() => ({ start: vi.fn(), stop: vi.fn(), })), })); vi.mock("@atproto/oauth-client-node", () => ({ NodeOAuthClient: vi.fn(() => ({ clientMetadata: {}, })), })); vi.mock("../oauth-stores.js", () => ({ OAuthStateStore: vi.fn(() => ({ destroy: vi.fn() })), OAuthSessionStore: vi.fn(() => ({ destroy: vi.fn() })), })); vi.mock("../cookie-session-store.js", () => ({ CookieSessionStore: vi.fn(() => ({ destroy: vi.fn() })), })); vi.mock("@atbb/atproto", () => ({ ForumAgent: vi.fn(() => ({ initialize: vi.fn(), shutdown: vi.fn(), isAuthenticated: vi.fn(() => true), })), })); vi.mock("@atbb/logger", () => ({ createLogger: vi.fn(() => ({ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), fatal: vi.fn(), child: vi.fn(() => ({ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), fatal: vi.fn(), child: vi.fn(), shutdown: vi.fn(), })), shutdown: vi.fn(), })), })); describe("AppContext", () => { let config: AppConfig; beforeEach(() => { config = { port: 3000, forumDid: "did:plc:test123", databaseUrl: "postgres://localhost/test", jetstreamUrl: "wss://jetstream.example.com", pdsUrl: "https://pds.example.com", logLevel: "info", oauthPublicUrl: "http://localhost:3000", sessionSecret: "test-secret-with-minimum-32-chars-for-validation", sessionTtlDays: 7, forumHandle: "forum.example.com", forumPassword: "test-password", backfillRateLimit: 10, backfillConcurrency: 10, backfillCursorMaxAgeHours: 48, }; }); describe("createAppContext", () => { it("creates ForumAgent when credentials are provided", async () => { const ctx = await createAppContext(config); expect(ctx.forumAgent).toBeDefined(); expect(ctx.forumAgent).not.toBeNull(); expect(ctx.forumAgent!.initialize).toHaveBeenCalled(); }); it("sets forumAgent to null when credentials are missing", async () => { config.forumHandle = undefined; config.forumPassword = undefined; const ctx = await createAppContext(config); expect(ctx.forumAgent).toBeNull(); expect(ctx.logger.warn).toHaveBeenCalledWith( "ForumAgent credentials missing", expect.objectContaining({ operation: "createAppContext" }) ); }); }); describe("destroyAppContext", () => { it("shuts down ForumAgent if present", async () => { const ctx = await createAppContext(config); const shutdownSpy = vi.spyOn(ctx.forumAgent!, "shutdown"); await destroyAppContext(ctx); expect(shutdownSpy).toHaveBeenCalled(); }); it("handles null ForumAgent gracefully", async () => { config.forumHandle = undefined; config.forumPassword = undefined; const ctx = await createAppContext(config); await expect(destroyAppContext(ctx)).resolves.not.toThrow(); }); }); });