Skip to content

Commit e8c8001

Browse files
d-csclaude
andcommitted
test(webapp): stop unit tests reaching env-configured Redis/Postgres in CI
CI runners have no .env, no REDIS_HOST/REDIS_PORT, and no Postgres at localhost:5432, which surfaced two failure layers that local runs mask (the dev stack answers on both): - suites transitively importing triggerTaskV1.server failed to collect because autoIncrementCounter.server.ts throws at import when REDIS_HOST/REDIS_PORT are unset (shards 2/5/6). Default the pair in test/setup.ts — the global ioredis lazyConnect mock means nothing dials. - TriggerFailedTaskService.call() resolved its event repository via getEventRepository → global prisma (feature-flag read + Prisma event repo), so in CI the swallowed connect error returned null friendlyIds (shard 8). Allow injecting the repository/store pair and bind the test to an EventRepository over the testcontainer DB. - once the cancelDevSessionRuns suite could collect, findLatestSession's hardwired global $replica was the next masked layer; give it an injectable client (defaulting to $replica) and pass the service's _replica through. Verified by replaying the exact CI env locally (.env hidden, workflow env vars, dead localhost DB, GITHUB_ACTIONS set): all four failing suites and full shards 2/5/6/8 reproduce the CI failures before and pass after. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent a03aad1 commit e8c8001

6 files changed

Lines changed: 53 additions & 38 deletions

File tree

apps/webapp/app/models/runtimeEnvironment.server.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,8 +358,11 @@ export async function disconnectSession(environmentId: string) {
358358
return session;
359359
}
360360

361-
export async function findLatestSession(environmentId: string) {
362-
const session = await $replica.runtimeEnvironmentSession.findFirst({
361+
export async function findLatestSession(
362+
environmentId: string,
363+
client: PrismaClientOrTransaction = $replica
364+
) {
365+
const session = await client.runtimeEnvironmentSession.findFirst({
363366
where: {
364367
environmentId,
365368
},

apps/webapp/app/runEngine/services/triggerFailedTask.server.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { resolveInheritedMintKind } from "~/v3/runOpsMigration/resolveInheritedM
1313
import { getEventRepository } from "~/v3/eventRepository/index.server";
1414
import { runStore as defaultRunStore } from "~/v3/runStore.server";
1515
import type { RunStore } from "@internal/run-store";
16+
import type { IEventRepository } from "~/v3/eventRepository/eventRepository.types";
1617
import { PerformTaskRunAlertsService } from "~/v3/services/alerts/performTaskRunAlerts.server";
1718
import { DefaultQueueManager } from "../concerns/queues.server";
1819
import type { TriggerTaskRequest } from "../types";
@@ -65,17 +66,22 @@ export class TriggerFailedTaskService {
6566
// singleton (in production the same store the engine writes through). Injected in
6667
// tests so the read resolves on the same store the engine wrote to.
6768
private readonly runStore: RunStore;
69+
// Defaults to getEventRepository's org-flag resolution, which reads through the
70+
// global prisma client; tests inject a repository bound to their testcontainer DB.
71+
private readonly eventRepository?: { repository: IEventRepository; store: string };
6872

6973
constructor(opts: {
7074
prisma: PrismaClientOrTransaction;
7175
engine: RunEngine;
7276
replicaPrisma?: PrismaClientOrTransaction;
7377
runStore?: RunStore;
78+
eventRepository?: { repository: IEventRepository; store: string };
7479
}) {
7580
this.prisma = opts.prisma;
7681
this.replicaPrisma = opts.replicaPrisma ?? opts.prisma;
7782
this.engine = opts.engine;
7883
this.runStore = opts.runStore ?? defaultRunStore;
84+
this.eventRepository = opts.eventRepository;
7985
}
8086

8187
// Mint a failed run's friendlyId. The id-kind decides which store the run is
@@ -122,11 +128,13 @@ export class TriggerFailedTaskService {
122128
});
123129
mintedFriendlyId = failedRunFriendlyId;
124130

125-
const { repository, store } = await getEventRepository(
126-
request.environment.organization.id,
127-
request.environment.organization.featureFlags as Record<string, unknown>,
128-
undefined
129-
);
131+
const { repository, store } =
132+
this.eventRepository ??
133+
(await getEventRepository(
134+
request.environment.organization.id,
135+
request.environment.organization.featureFlags as Record<string, unknown>,
136+
undefined
137+
));
130138

131139
// Resolve parent run for rootTaskRunId and depth (same as triggerTask.server.ts)
132140
const parentRun = request.parentRunId

apps/webapp/app/v3/services/cancelDevSessionRuns.server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export class CancelDevSessionRunsService extends BaseService {
4343
: undefined;
4444

4545
if (cancelledSession) {
46-
const latestSession = await findLatestSession(cancelledSession.environmentId);
46+
const latestSession = await findLatestSession(cancelledSession.environmentId, this._replica);
4747

4848
if (
4949
latestSession &&

apps/webapp/test/cancelDevSessionRunsStoreRouting.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ describe("CancelDevSessionRunsService passthrough (single-DB)", () => {
226226
// control-plane read runs on the same prisma.
227227
const service = new CancelDevSessionRunsService({
228228
prisma,
229+
replica: prisma,
229230
readThroughDeps: {
230231
splitEnabled: false,
231232
newClient: prisma as unknown as PrismaReplicaClient,

apps/webapp/test/engine/triggerFailedTask.test.ts

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,31 @@ import { containerTest } from "@internal/testcontainers";
66
import { trace } from "@opentelemetry/api";
77
import { RunId, classifyKind, generateKsuidId } from "@trigger.dev/core/v3/isomorphic";
88
import { TriggerFailedTaskService } from "../../app/runEngine/services/triggerFailedTask.server";
9+
import { EventRepository } from "../../app/v3/eventRepository/eventRepository.server";
910

1011
vi.setConfig?.({ testTimeout: 60_000 });
1112

13+
// Bind the service's trace-event writes to the testcontainer DB. Without this,
14+
// call() resolves the repository via getEventRepository → global prisma, which
15+
// points at a database that doesn't exist in CI.
16+
function makeService(prisma: any, engine: RunEngine) {
17+
return new TriggerFailedTaskService({
18+
prisma,
19+
engine,
20+
// Read the parent through the same store the engine wrote it to.
21+
runStore: engine.runStore,
22+
eventRepository: {
23+
repository: new EventRepository(prisma, prisma, {
24+
batchSize: 100,
25+
batchInterval: 1000,
26+
retentionInDays: 30,
27+
partitioningEnabled: false,
28+
}),
29+
store: "taskEvent",
30+
},
31+
});
32+
}
33+
1234
function makeEngine(prisma: any, redisOptions: any) {
1335
return new RunEngine({
1436
prisma,
@@ -40,12 +62,7 @@ describe("TriggerFailedTaskService — failed run residency", () => {
4062
const taskIdentifier = "failed-residency-task";
4163
await setupBackgroundWorker(engine, environment, taskIdentifier);
4264

43-
const service = new TriggerFailedTaskService({
44-
prisma,
45-
engine,
46-
// Read the parent through the same store the engine wrote it to.
47-
runStore: engine.runStore,
48-
});
65+
const service = makeService(prisma, engine);
4966

5067
const friendlyId = await service.call({
5168
taskId: taskIdentifier,
@@ -95,12 +112,7 @@ describe("TriggerFailedTaskService — failed run residency", () => {
95112
prisma
96113
);
97114

98-
const service = new TriggerFailedTaskService({
99-
prisma,
100-
engine,
101-
// Read the parent through the same store the engine wrote it to.
102-
runStore: engine.runStore,
103-
});
115+
const service = makeService(prisma, engine);
104116

105117
const friendlyId = await service.call({
106118
taskId: taskIdentifier,
@@ -153,12 +165,7 @@ describe("TriggerFailedTaskService — failed run residency", () => {
153165
prisma
154166
);
155167

156-
const service = new TriggerFailedTaskService({
157-
prisma,
158-
engine,
159-
// Read the parent through the same store the engine wrote it to.
160-
runStore: engine.runStore,
161-
});
168+
const service = makeService(prisma, engine);
162169

163170
const friendlyId = await service.call({
164171
taskId: taskIdentifier,
@@ -200,12 +207,7 @@ describe("TriggerFailedTaskService — failed run residency", () => {
200207
prisma
201208
);
202209

203-
const service = new TriggerFailedTaskService({
204-
prisma,
205-
engine,
206-
// Read the parent through the same store the engine wrote it to.
207-
runStore: engine.runStore,
208-
});
210+
const service = makeService(prisma, engine);
209211

210212
const friendlyId = await service.callWithoutTraceEvents({
211213
environmentId: environment.id,
@@ -232,12 +234,7 @@ describe("TriggerFailedTaskService — failed run residency", () => {
232234
const taskIdentifier = "failed-residency-task";
233235
await setupBackgroundWorker(engine, environment, taskIdentifier);
234236

235-
const service = new TriggerFailedTaskService({
236-
prisma,
237-
engine,
238-
// Read the parent through the same store the engine wrote it to.
239-
runStore: engine.runStore,
240-
});
237+
const service = makeService(prisma, engine);
241238

242239
// A well-formed ksuid parent friendlyId that was NEVER triggered → no row.
243240
// Exercises the missing-parent fallback in callWithoutTraceEvents.

apps/webapp/test/setup.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ import { vi } from "vitest";
66

77
config({ path: path.resolve(__dirname, "../.env") });
88

9+
// CI has no .env and no REDIS_HOST/REDIS_PORT, so import-time guards like
10+
// autoIncrementCounter.server.ts throw and their suites fail to collect. Default
11+
// the pair — the ioredis mock below forces lazyConnect, so nothing ever dials.
12+
process.env.REDIS_HOST ??= "localhost";
13+
process.env.REDIS_PORT ??= "6379";
14+
915
// Worker singletons construct a RedisWorker at import time whose ioredis client
1016
// connects eagerly, so any test importing the service graph opens real Redis
1117
// connections on import — which floods and fails in CI (no Redis). Mock them to

0 commit comments

Comments
 (0)