-
-
Notifications
You must be signed in to change notification settings - Fork 853
Add @trigger.dev/test package #2072
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from 1 commit
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
# @trigger.dev/test | ||
|
||
Testing utilities for trigger.dev tasks. | ||
|
||
## Installation | ||
|
||
```bash | ||
npm install --save-dev @trigger.dev/test | ||
``` | ||
|
||
## Usage | ||
|
||
### Creating a mock task | ||
|
||
```typescript | ||
import { mockTask } from "@trigger.dev/test"; | ||
|
||
const task = mockTask({ | ||
id: "test-task", | ||
output: { success: true }, | ||
}); | ||
|
||
// Use the mock task in your tests | ||
const result = await task.triggerAndWait({}).unwrap(); | ||
console.log(result); // { success: true } | ||
``` | ||
|
||
### Unit testing a task with mocked dependencies | ||
|
||
```typescript | ||
import { mockTask, testTaskFunction } from "@trigger.dev/test"; | ||
|
||
// Create a mock for a dependent task | ||
const dependentTask = mockTask({ | ||
id: "dependent-task", | ||
output: { result: "mocked-result" }, | ||
}); | ||
|
||
// Test a task that uses the dependent task | ||
const result = await testTaskFunction(mainTask, { input: "test" }, {}, { | ||
mockDependencies: [dependentTask] | ||
}); | ||
|
||
// Verify the result | ||
expect(result).toEqual({ | ||
mainResult: "Processed: mocked-result", | ||
input: "test" | ||
}); | ||
``` | ||
|
||
### Setting up a test environment with mocked tasks | ||
|
||
```typescript | ||
import { setupMockTaskEnvironment, mockTask } from "@trigger.dev/test"; | ||
|
||
// Set up the test environment | ||
const cleanup = setupMockTaskEnvironment((registry) => { | ||
// Register mock tasks | ||
registry.registerMockTask(mockTask({ | ||
id: "task-1", | ||
output: { result: "mocked-result-1" }, | ||
})); | ||
|
||
registry.registerMockTask(mockTask({ | ||
id: "task-2", | ||
output: { result: "mocked-result-2" }, | ||
})); | ||
}); | ||
|
||
// Run your tests... | ||
|
||
// Clean up | ||
cleanup(); | ||
``` | ||
|
||
### Verifying task triggers | ||
|
||
```typescript | ||
import { mockTask, verifyTaskTriggered, getTaskTriggerCount } from "@trigger.dev/test"; | ||
|
||
const task = mockTask({ | ||
id: "test-task", | ||
output: { success: true }, | ||
}); | ||
|
||
// Trigger the task | ||
await task.trigger({ data: "test" }); | ||
|
||
// Verify the task was triggered | ||
console.log(verifyTaskTriggered(task)); // true | ||
console.log(verifyTaskTriggered(task, { data: "test" })); // true | ||
console.log(getTaskTriggerCount(task)); // 1 | ||
``` | ||
|
||
### Testing hooks | ||
|
||
```typescript | ||
import { mockTask, createMockHooks, verifyHookCalled } from "@trigger.dev/test"; | ||
|
||
const task = mockTask({ | ||
id: "test-task", | ||
output: { success: true }, | ||
}); | ||
|
||
const hooks = createMockHooks(task); | ||
|
||
// Call the hooks | ||
await hooks.onStart({ data: "test" }, {} as any); | ||
await hooks.onSuccess({ data: "test" }, { success: true }); | ||
|
||
// Verify the hooks were called | ||
console.log(verifyHookCalled(task, "onStartCalled")); // true | ||
console.log(verifyHookCalled(task, "onSuccessCalled")); // true | ||
``` | ||
|
||
## API Reference | ||
|
||
### Task Mocking | ||
|
||
- `mockTask(options)`: Creates a mock task that can be used for testing. | ||
- `isMockTask(task)`: Checks if a task is a mock task. | ||
- `createMockTaskForUnitTest(options)`: Creates a mock task with a custom run function. | ||
- `mockTaskDependencies(taskToTest, dependencies)`: Mocks dependencies for a specific task. | ||
|
||
### Task Testing | ||
|
||
- `executeMockTask(task, payload, options)`: Executes a task with mocked dependencies. | ||
- `testTaskFunction(task, payload, params, options)`: Tests a task's run function directly. | ||
- `setupMockTaskEnvironment(setupFn)`: Sets up a test environment with mocked tasks. | ||
|
||
### Trigger Verification | ||
|
||
- `verifyTaskTriggered(task, expectedPayload)`: Verifies that a task was triggered with the expected payload. | ||
- `getTaskTriggerCount(task)`: Gets the number of times a task was triggered. | ||
- `clearAllMockTriggers()`: Clears all mock triggers. | ||
|
||
### Hook Testing | ||
|
||
- `createMockHooks(task)`: Creates mock hooks for a task. | ||
- `verifyHookCalled(task, hookType)`: Verifies that a specific hook was called for a task. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
{ | ||
"name": "@trigger.dev/test", | ||
"version": "4.0.0-v4-beta.16", | ||
"description": "Testing utilities for trigger.dev tasks", | ||
"license": "MIT", | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/triggerdotdev/trigger.dev", | ||
"directory": "packages/test" | ||
}, | ||
"type": "module", | ||
"files": [ | ||
"dist" | ||
], | ||
"tshy": { | ||
"selfLink": false, | ||
"main": true, | ||
"module": true, | ||
"project": "./tsconfig.build.json", | ||
"exports": { | ||
"./package.json": "./package.json", | ||
".": "./src/index.ts" | ||
}, | ||
"sourceDialects": [ | ||
"@triggerdotdev/source" | ||
] | ||
}, | ||
"typesVersions": { | ||
"*": { | ||
".": [ | ||
"dist/commonjs/index.d.ts" | ||
] | ||
} | ||
}, | ||
"scripts": { | ||
"clean": "rimraf dist .tshy .tshy-build .turbo", | ||
"build": "tshy && pnpm run update-version", | ||
"dev": "tshy --watch", | ||
"typecheck": "tsc --noEmit", | ||
"update-version": "tsx ../../scripts/updateVersion.ts", | ||
"check-exports": "attw --pack .", | ||
"test": "vitest" | ||
}, | ||
"dependencies": { | ||
"@trigger.dev/core": "workspace:4.0.0-v4-beta.16" | ||
}, | ||
"devDependencies": { | ||
"@arethetypeswrong/cli": "^0.15.4", | ||
"@trigger.dev/sdk": "workspace:4.0.0-v4-beta.16", | ||
"rimraf": "^3.0.2", | ||
"tshy": "^3.0.2", | ||
"tsx": "4.17.0", | ||
"vitest": "^1.6.0", | ||
"zod": "3.23.8" | ||
}, | ||
"peerDependencies": { | ||
"@trigger.dev/sdk": "^4.0.0-v4-beta.16", | ||
"zod": "^3.0.0" | ||
}, | ||
"engines": { | ||
"node": ">=18.20.0" | ||
}, | ||
"exports": { | ||
"./package.json": "./package.json", | ||
".": { | ||
"import": { | ||
"@triggerdotdev/source": "./src/index.ts", | ||
"types": "./dist/esm/index.d.ts", | ||
"default": "./dist/esm/index.js" | ||
}, | ||
"require": { | ||
"types": "./dist/commonjs/index.d.ts", | ||
"default": "./dist/commonjs/index.js" | ||
} | ||
} | ||
}, | ||
"main": "./dist/commonjs/index.js", | ||
"types": "./dist/commonjs/index.d.ts", | ||
"module": "./dist/esm/index.js" | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { mockTask, isMockTask, createMockTaskForUnitTest, mockTaskDependencies } from "./mockTask.js"; | ||
import { executeMockTask, testTaskFunction } from "./taskTesting.js"; | ||
import { setupMockTaskEnvironment } from "./mockTaskRegistry.js"; | ||
import { verifyTaskTriggered, getTaskTriggerCount, clearAllMockTriggers } from "./mockTrigger.js"; | ||
import { createMockHooks, verifyHookCalled } from "./mockHooks.js"; | ||
|
||
export * from "./types.js"; | ||
export * from "./mockTask.js"; | ||
export * from "./mockTrigger.js"; | ||
export * from "./mockHooks.js"; | ||
export * from "./taskTesting.js"; | ||
export * from "./mockTaskRegistry.js"; | ||
|
||
export const triggerTest = { | ||
mockTask, | ||
isMockTask, | ||
createMockTaskForUnitTest, | ||
mockTaskDependencies, | ||
|
||
executeMockTask, | ||
testTaskFunction, | ||
|
||
setupMockTaskEnvironment, | ||
|
||
verifyTaskTriggered, | ||
getTaskTriggerCount, | ||
clearAllMockTriggers, | ||
|
||
createMockHooks, | ||
verifyHookCalled, | ||
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import { Task, TaskIdentifier, TaskOutput, TaskPayload } from "@trigger.dev/sdk/v3"; | ||
import { MockHooksCallInfo, TaskRunContext } from "./types.js"; | ||
|
||
/** | ||
* A registry of all hook calls | ||
*/ | ||
class MockHooksRegistry { | ||
private hookCalls: Record<string, MockHooksCallInfo> = {}; | ||
|
||
/** | ||
* Record a hook call | ||
*/ | ||
recordHookCall<TPayload = any, TOutput = any, TError = any>( | ||
taskId: string, | ||
hookType: keyof MockHooksCallInfo, | ||
payload?: TPayload, | ||
output?: TOutput, | ||
error?: TError | ||
) { | ||
if (!this.hookCalls[taskId]) { | ||
this.hookCalls[taskId] = { | ||
onStartCalled: false, | ||
onSuccessCalled: false, | ||
onFailureCalled: false, | ||
onCompleteCalled: false, | ||
}; | ||
} | ||
|
||
const hookCallInfo = this.hookCalls[taskId]; | ||
|
||
hookCallInfo[hookType] = true; | ||
|
||
if (payload !== undefined) { | ||
hookCallInfo.payload = payload; | ||
} | ||
|
||
if (output !== undefined) { | ||
hookCallInfo.output = output; | ||
} | ||
|
||
if (error !== undefined) { | ||
hookCallInfo.error = error; | ||
} | ||
} | ||
|
||
/** | ||
* Get hook calls for a specific task | ||
*/ | ||
getHookCallsForTask(taskId: string): MockHooksCallInfo | undefined { | ||
return this.hookCalls[taskId]; | ||
} | ||
|
||
/** | ||
* Clear all hook calls | ||
*/ | ||
clearHookCalls() { | ||
this.hookCalls = {}; | ||
} | ||
} | ||
|
||
export const mockHooksRegistry = new MockHooksRegistry(); | ||
|
||
/** | ||
* Create mock hooks for a task | ||
*/ | ||
export function createMockHooks< | ||
TIdentifier extends string, | ||
TInput = void, | ||
TOutput = unknown | ||
>( | ||
task: Task<TIdentifier, TInput, TOutput> | ||
) { | ||
return { | ||
onStart: async (payload: TInput, ctx: TaskRunContext) => { | ||
mockHooksRegistry.recordHookCall(task.id, "onStartCalled", payload); | ||
}, | ||
onSuccess: async (payload: TInput, output: TOutput) => { | ||
mockHooksRegistry.recordHookCall(task.id, "onSuccessCalled", payload, output); | ||
}, | ||
onFailure: async (payload: TInput, error: Error) => { | ||
mockHooksRegistry.recordHookCall(task.id, "onFailureCalled", payload, undefined, error); | ||
}, | ||
onComplete: async (payload: TInput, result: { ok: boolean; data?: TOutput; error?: Error }) => { | ||
mockHooksRegistry.recordHookCall( | ||
task.id, | ||
"onCompleteCalled", | ||
payload, | ||
result.ok ? result.data : undefined, | ||
result.ok ? undefined : result.error | ||
); | ||
}, | ||
}; | ||
} | ||
|
||
/** | ||
* Verify that a specific hook was called for a task | ||
*/ | ||
export function verifyHookCalled< | ||
TIdentifier extends string, | ||
TInput = void, | ||
TOutput = unknown | ||
>( | ||
task: Task<TIdentifier, TInput, TOutput>, | ||
hookType: keyof Omit<MockHooksCallInfo, "payload" | "output" | "error"> | ||
): boolean { | ||
const hookCalls = mockHooksRegistry.getHookCallsForTask(task.id); | ||
|
||
if (!hookCalls) { | ||
return false; | ||
} | ||
|
||
return hookCalls[hookType]; | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Check warning
Code scanning / CodeQL
Prototype-polluting assignment Medium test
Copilot Autofix
AI 5 months ago
To fix the issue, we need to ensure that
taskId
cannot be a value like__proto__
,constructor
, orprototype
that could lead to prototype pollution. This can be achieved by validatingtaskId
before using it as a key in thehookCalls
object. IftaskId
contains any of these reserved values, the method should throw an error or handle the case appropriately.The best approach is to add a validation step at the beginning of the
recordHookCall
method to check iftaskId
is one of the reserved keys. If it is, the method should throw an error or return early without making any changes.