-
Notifications
You must be signed in to change notification settings - Fork 3
Add local-runner for extraction testing and improve test loading #74
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
Merged
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
66546b9
Initial implementation of Fixture testing.
gasperzgonec 6e64ff3
Added fixtures and added support for variable interpolation.
gasperzgonec c419678
Added printState to enable tracking of state updates.
gasperzgonec 524be92
Fixed suggestions reported by Copilot and migrated from EventContext …
gasperzgonec 689106d
Fixed Copilot notes.
gasperzgonec 2a824d8
Update code/src/main.ts
gasperzgonec a5df57d
Renamed test-runner to local-runner, renamed fixture files and remove…
gasperzgonec 1d56671
Renamed the actual files
gasperzgonec d2d53c8
Merge branch 'main' into offline_testing
gasperzgonec 530fd3a
Removed state.json from external sync unit extraction.
gasperzgonec dca020e
Removed printState
gasperzgonec 3aaef54
Renamed local-runner to test-runner to reduce change footprint
gasperzgonec a3a2512
Renamed the `run:local` script to `fixture`.
gasperzgonec dc0adb4
Added an .env.example file to show people where to store env variables.
gasperzgonec 4345ec1
Merge branch 'main' into offline_testing
gasperzgonec 093e835
Implemented extraction_scope.json file
gasperzgonec 0978457
Added first batch of PR reviews
gasperzgonec 80810d3
Reverted changes to the main.ts, but kept functionName optional.
gasperzgonec 77f8d90
Updated test-runner to match the new createMockEvent interface.
gasperzgonec 4813deb
Removed FixtureEvent and replaced it with the AirdropEvent.
gasperzgonec eea521c
Hardcode the local flag to provide user-friendly output to the terminal.
gasperzgonec 36c2a50
Removed `fixture` action from the package.json
gasperzgonec 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,2 @@ | ||
| # TODO: add External system specific variables that will be replaced in the Fixture tests that you want to run. | ||
| TODO_API_KEY=test-api-key | ||
|
radovanjorgic marked this conversation as resolved.
|
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,14 @@ | ||
| { | ||
| "connection_data": { | ||
| "org_id": "test_org", | ||
| "org_name": "Test Organization", | ||
| "key": "${TODO_API_KEY}", | ||
| "key_type": "pat" | ||
| }, | ||
| "payload": { | ||
| "event_type": "START_EXTRACTING_DATA" | ||
| }, | ||
| "event_context": { | ||
| "external_sync_unit_id": "test_external_sync_unit_id" | ||
| } | ||
| } | ||
|
radovanjorgic marked this conversation as resolved.
|
||
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,11 @@ | ||
| { | ||
| "todos": { | ||
| "extract": true | ||
| }, | ||
| "users": { | ||
| "extract": true | ||
| }, | ||
| "attachments": { | ||
| "extract": true | ||
| } | ||
| } |
|
radovanjorgic marked this conversation as resolved.
|
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,5 @@ | ||
| { | ||
| "todos": { "completed": false }, | ||
| "users": { "completed": false }, | ||
| "attachments": { "completed": false } | ||
| } |
12 changes: 12 additions & 0 deletions
12
code/fixtures/start_extracting_external_sync_units/event.json
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,12 @@ | ||
| { | ||
| "function_name": "extraction", | ||
| "payload": { | ||
| "event_type": "START_EXTRACTING_EXTERNAL_SYNC_UNITS" | ||
| }, | ||
| "connection_data": { | ||
| "org_id": "test_org", | ||
| "org_name": "Test Organization", | ||
| "key": "test-api-key", | ||
| "key_type": "pat" | ||
| } | ||
| } |
This file was deleted.
Oops, something went wrong.
|
radovanjorgic marked this conversation as resolved.
|
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
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 |
|---|---|---|
| @@ -1,45 +1,147 @@ | ||
| import { AirdropEvent } from '@devrev/ts-adaas'; | ||
| import * as dotenv from 'dotenv'; | ||
|
radovanjorgic marked this conversation as resolved.
|
||
| import * as fs from 'fs'; | ||
| import * as path from 'path'; | ||
|
|
||
| import { AirdropEvent, createMockEvent, DeepPartial, EventType, MockServer } from '@devrev/ts-adaas'; | ||
|
|
||
| import { functionFactory, FunctionFactoryType } from '../function-factory'; | ||
|
|
||
| export interface TestRunnerProps { | ||
| functionName: FunctionFactoryType; | ||
| export interface LocalRunnerProps { | ||
| fixturePath: string; | ||
| functionName?: FunctionFactoryType; | ||
| } | ||
| export function addCredentials(events: AirdropEvent[], env: dotenv.DotenvParseOutput): AirdropEvent[] { | ||
| return events.map((event: AirdropEvent) => { | ||
| return { | ||
| ...event, | ||
| context: { | ||
| ...event.context, | ||
| secrets: { | ||
| ...event.context.secrets, | ||
| service_account_token: env['DEVREV_PAT'], | ||
| }, | ||
| }, | ||
| }; | ||
|
|
||
| /** | ||
| * Replaces `${VAR_NAME}` placeholders with values from `process.env`. | ||
| * Values are JSON-escaped so special characters don't break the JSON structure. | ||
| */ | ||
| function resolveEnvVariables(raw: string, filePath: string): string { | ||
|
radovanjorgic marked this conversation as resolved.
|
||
| return raw.replace(/\$\{(\w+)\}/g, (_match, varName: string) => { | ||
| const value = process.env[varName]; | ||
| if (value === undefined) { | ||
| throw new Error( | ||
| `Environment variable "${varName}" referenced in ${filePath} is not set. ` + | ||
| 'Make sure it is defined in your .env file or exported in your shell.' | ||
| ); | ||
| } | ||
| return JSON.stringify(value).slice(1, -1); | ||
| }); | ||
| } | ||
|
|
||
| export const testRunner = async ({ functionName, fixturePath }: TestRunnerProps) => { | ||
| const env = dotenv.config(); | ||
| function readFixtureFile<T>(filePath: string): T | undefined { | ||
| if (!fs.existsSync(filePath)) { | ||
| return undefined; | ||
| } | ||
| const raw = fs.readFileSync(filePath, 'utf-8').trim(); | ||
| if (raw.length === 0) { | ||
| return undefined; | ||
| } | ||
| const resolved = resolveEnvVariables(raw, filePath); | ||
| return JSON.parse(resolved) as T; | ||
| } | ||
|
gasperzgonec marked this conversation as resolved.
|
||
|
|
||
| function resolveEventType(input: string): EventType { | ||
|
radovanjorgic marked this conversation as resolved.
|
||
| const match = Object.values(EventType).find((v) => v === input); | ||
| if (match) return match as EventType; | ||
|
|
||
| throw new Error(`Unknown event_type "${input}". Must be one of: ${Object.values(EventType).join(', ')}`); | ||
| } | ||
|
|
||
| export const testRunner = async ({ fixturePath, functionName }: LocalRunnerProps) => { | ||
| dotenv.config(); | ||
|
|
||
| console.log('env:', env); | ||
| const fixturesDir = path.resolve(__dirname, '../../fixtures', fixturePath); | ||
| if (!fs.existsSync(fixturesDir)) { | ||
| throw new Error(`Fixture directory not found: ${fixturesDir}`); | ||
| } | ||
| return runWithFixtureDir(fixturesDir, functionName); | ||
| }; | ||
|
|
||
| if (!functionFactory[functionName]) { | ||
| console.error(`${functionName} is not found in the functionFactory`); | ||
| console.error('Add your function to the function-factory.ts file'); | ||
| throw new Error('Function is not found in the functionFactory'); | ||
| function getFunctionName(event_type: string): FunctionFactoryType { | ||
| if (event_type.indexOf('EXTRACT') != -1) { | ||
| return 'extraction'; | ||
| } else if (event_type.indexOf('LOAD') != -1) { | ||
| return 'loading'; | ||
| } | ||
|
|
||
| const run = functionFactory[functionName]; | ||
| throw new Error(`No functionName found for event ${event_type}. Specify functionName using '--functionName' parameter.`); | ||
| } | ||
|
|
||
| async function runWithFixtureDir(fixturesDir: string, functionName?: FunctionFactoryType) { | ||
| const eventPath = path.join(fixturesDir, 'event.json'); | ||
| const statePath = path.join(fixturesDir, 'state.json'); | ||
| const extractionScopePath = path.join(fixturesDir, 'extraction_scope.json'); | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/no-require-imports | ||
| const eventFixture = require(`../fixtures/${fixturePath}`); | ||
| const fixtureEvent = readFixtureFile<DeepPartial<AirdropEvent>>(eventPath); | ||
| const fixtureState = readFixtureFile<Record<string, unknown>>(statePath); | ||
| const fixtureExtractionScope = readFixtureFile<Record<string, unknown>>(extractionScopePath); | ||
|
|
||
| if (!fixtureEvent) { | ||
| throw new Error( | ||
| `Missing or empty event.json in fixture directory: ${eventPath}. ` + | ||
| 'Every fixture must have an event.json with at least an "event_type" field.' | ||
| ); | ||
| } | ||
|
|
||
| if (!fixtureEvent.payload?.event_type) { | ||
| throw new Error( | ||
| `event.json at ${eventPath} is missing the required "event_type" field. ` + | ||
| 'Specify an event type such as "START_EXTRACTING_DATA" or "START_EXTRACTING_EXTERNAL_SYNC_UNITS".' | ||
| ); | ||
| } | ||
|
|
||
| if (env.parsed) { | ||
| await run(addCredentials(eventFixture, env.parsed)); | ||
| const resolvedFunctionName = functionName ?? getFunctionName(fixtureEvent.payload?.event_type); | ||
|
|
||
| if (!resolvedFunctionName) { | ||
| throw new Error( | ||
| 'No function name provided. Either pass --functionName on the CLI ' + 'or set "function_name" in event.json.' | ||
| ); | ||
| } | ||
|
|
||
| if (!(resolvedFunctionName in functionFactory)) { | ||
| throw new Error( | ||
| `Function "${resolvedFunctionName}" not found in functionFactory. ` + | ||
| `Available: ${Object.keys(functionFactory).join(', ')}` | ||
| ); | ||
| } | ||
|
|
||
| const eventType = resolveEventType(fixtureEvent.payload?.event_type); | ||
|
|
||
| console.log(`[test-runner] Function : ${resolvedFunctionName}`); | ||
| console.log(`[test-runner] Event : ${eventType}`); | ||
| console.log(`[test-runner] Fixture : ${fixturesDir}`); | ||
|
|
||
| const mockServer = new MockServer(0); | ||
| await mockServer.start(); | ||
|
|
||
| console.log(`[test-runner] MockServer started on ${mockServer.baseUrl}`); | ||
|
|
||
| mockServer.setRoute({ | ||
|
radovanjorgic marked this conversation as resolved.
|
||
| path: '/worker_data_url.get', | ||
| method: 'GET', | ||
| status: 200, | ||
| body: { state: JSON.stringify(fixtureState ?? {}), objects: JSON.stringify(fixtureExtractionScope ?? {}) }, | ||
| }); | ||
| if (fixtureState) { | ||
| console.log(`[test-runner] Injected state from state.json`); | ||
| } else { | ||
|
gasperzgonec marked this conversation as resolved.
|
||
| await run(eventFixture); | ||
| console.log(`[test-runner] No state.json found — using default empty state`); | ||
| } | ||
| }; | ||
|
|
||
| const event = createMockEvent(mockServer.baseUrl, fixtureEvent); | ||
|
|
||
| process.argv.push("--local"); | ||
|
|
||
| const run = functionFactory[resolvedFunctionName]; | ||
|
|
||
| try { | ||
| await run([event]); | ||
| console.log(`[test-runner] Function completed successfully`); | ||
| } catch (err) { | ||
| console.error(`[test-runner] Function threw an error:`, err); | ||
| process.exitCode = 1; | ||
| } finally { | ||
| await mockServer.stop(); | ||
| console.log(`[test-runner] MockServer stopped`); | ||
| } | ||
| } | ||
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.
Uh oh!
There was an error while loading. Please reload this page.