diff --git a/apps/api/src/__tests__/app-module.compile.spec.ts b/apps/api/src/__tests__/app-module.compile.spec.ts new file mode 100644 index 0000000..6b1db89 --- /dev/null +++ b/apps/api/src/__tests__/app-module.compile.spec.ts @@ -0,0 +1,44 @@ +import { Test } from "@nestjs/testing"; +import { beforeAll, describe, expect, it } from "vitest"; + +/** + * Smoke test that resolves the entire AppModule provider graph. Catches the + * class of bugs where a module is missing an `imports`/`exports` entry that + * makes a downstream `@Inject` unresolvable - those mistakes type-check fine, + * pass unit tests (which `new` providers manually), and only surface at real + * NestJS DI boot time, i.e. in production. + * + * Caught two of these post-merge during the CVG-84 rollout: + * - AsyncJobStoreModule missing BaseModule import (ClockService unreachable) + * - AsyncJobGraphQLModule not re-exporting AsyncJobModule (interceptor + * consumers couldn't resolve AsyncJobService) + * + * The test only calls `.compile()` (resolves the graph) - it does NOT call + * `.init()` or `.listen()`, so lifecycle hooks (`onModuleInit`) and the + * actual Prisma connection don't fire. That keeps the spec fast and + * DB-independent. If you need to test lifecycle behaviour, build a + * dedicated integration spec under CVG-90. + */ +describe("AppModule (compile smoke test)", () => { + beforeAll(() => { + // Eager `configService.getOrThrow(...)` calls happen during provider + // instantiation; fill in fakes so `.compile()` doesn't trip on missing + // env vars. Values are nonsense - we're testing the provider graph, + // not the runtime behaviour. + process.env["DATABASE_URL"] = "postgresql://test:test@localhost:5432/test"; + process.env["JWT_SECRET"] = "test-jwt-secret-at-least-32-characters-long"; + process.env["JWT_ACCESS_TOKEN_EXPIRY"] = "15m"; + process.env["JWT_REFRESH_TOKEN_EXPIRY"] = "30d"; + process.env["ENCRYPTION_KEY"] = "test-encryption-key-at-least-32-chars-long"; + }); + + it("resolves every provider in the graph", async () => { + // Lazy import after env vars are set; AppModule's transitive `.forRoot` + // calls read env at module-load time. + const { AppModule } = await import("../modules/app.module"); + + await expect( + Test.createTestingModule({ imports: [AppModule] }).compile(), + ).resolves.toBeDefined(); + }); +}); diff --git a/apps/api/src/modules/async-job/async-job-graphql.module.ts b/apps/api/src/modules/async-job/async-job-graphql.module.ts index c3ac099..3a9637a 100644 --- a/apps/api/src/modules/async-job/async-job-graphql.module.ts +++ b/apps/api/src/modules/async-job/async-job-graphql.module.ts @@ -8,17 +8,13 @@ import { TypedAsyncJobInterceptor } from "./typed-async-job.interceptor"; * service) lives in `@cv/core` as `AsyncJobModule`; this module is the * api-only wiring for the `asyncJob` query and the bundle interceptor. * - * `AsyncJobModule` is re-exported because consumers of - * `TypedAsyncJobInterceptor` (via `@UseInterceptors` from the - * `createTypedAsyncJobBundle` decorator) need `AsyncJobService` resolvable - * in their own module scope - Nest resolves interceptor dependencies at - * the consumer site, not the provider site. Without the re-export, - * CVParserModule (which uses the bundle for `parseCvJob`) hits - * `UnknownDependenciesException` on `AsyncJobService` at app boot. + * Consumers needing the data layer (e.g. resolvers using the bundle in + * other feature modules) should import both this module (for the + * interceptor) AND `AsyncJobModule` from `@cv/core` directly. */ @Module({ imports: [AsyncJobModule], providers: [AsyncJobResolver, TypedAsyncJobInterceptor], - exports: [TypedAsyncJobInterceptor, AsyncJobModule], + exports: [TypedAsyncJobInterceptor], }) export class AsyncJobGraphQLModule {}