Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions bin/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { configure, processCLIArgs, run } from '@japa/runner'
import { expect } from '@japa/expect'
import { expectTypeOf } from '@japa/expect-type'

processCLIArgs(process.argv.splice(2))

configure({
files: ['tests/**/*.spec.ts'],
plugins: [expect(), expectTypeOf()],
})

run()
1 change: 1 addition & 0 deletions define_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export function defineConfig<Connections extends RedisConnections>(config: {
queueNameForWorkers: string
logger: string | null
verbose: boolean
jobsPath?: string | string[]
}) {
return config
}
15 changes: 12 additions & 3 deletions jobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,20 @@ import app from "@adonisjs/core/services/app"
import { fsImportAll } from "@poppinss/utils"
import Job from "./base_job.js"
import { NodeResqueJob } from './types.js'
import { getConfig } from './index.js'

export function resolveJobPaths(configured?: string | string[]): string[] {
if (!configured) return ['app/jobs']
return Array.isArray(configured) ? configured : [configured]
}

export async function importAllJobs() {
const jobs: Record<string, unknown> = await fsImportAll(app.makePath('app/jobs'), {
ignoreMissingRoot: true
})
const paths = resolveJobPaths(getConfig('jobsPath'))

const jobsArrays = await Promise.all(
paths.map((p) => fsImportAll(app.makePath(p), { ignoreMissingRoot: true }))
)
const jobs: Record<string, unknown> = Object.assign({}, ...jobsArrays)
/**
* Duck typing check
* @param job
Expand Down
7 changes: 7 additions & 0 deletions stubs/config/resque.stub
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ const resqueConfig = defineConfig({
*/
logger: null,
verbose: true,
/**
* path(s) to scan for job files, relative to the app root
* accepts a single path or an array of paths for modular architectures
* e.g. ['app/contacts/jobs', 'app/reminders/jobs']
* defaults to 'app/jobs' when omitted
*/
// jobsPath: 'app/jobs',
})

export default resqueConfig
7 changes: 7 additions & 0 deletions tests/fixtures/app/contacts/jobs/contact_job.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default class ContactJob {
plugins: any[] = []

async perform(_contactId: number) {}

async enqueue(_contactId: number) {}
}
7 changes: 7 additions & 0 deletions tests/fixtures/app/jobs/email_job.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default class EmailJob {
plugins: any[] = []

async perform(_to: string) {}

async enqueue(_to: string) {}
}
7 changes: 7 additions & 0 deletions tests/fixtures/app/reminders/jobs/reminder_job.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default class ReminderJob {
plugins: any[] = []

async perform(_message: string) {}

async enqueue(_message: string) {}
}
52 changes: 52 additions & 0 deletions tests/integration/import_all_jobs.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { test } from '@japa/runner'
import { fsImportAll } from '@poppinss/utils'
import { fileURLToPath } from 'node:url'
import { join } from 'node:path'

/**
* Tests the multi-path file loading logic that importAllJobs delegates to.
* We test fsImportAll + Object.assign directly because spinning up a full
* AdonisJS app in a library test is unnecessary — the wiring is a one-liner
* and the path resolution is already covered in the unit tests.
*/
const FIXTURES = fileURLToPath(new URL('../fixtures/', import.meta.url))

function resolveFixture(p: string) {
return join(FIXTURES, p)
}

test.group('multi-path job loading', () => {
test('discovers jobs from the default path', async ({ expect }) => {
const jobs = await fsImportAll(resolveFixture('app/jobs'), { ignoreMissingRoot: true })

const names = Object.values(jobs).map((v: any) => v.name)
expect(names).toContain('EmailJob')
})

test('discovers jobs from a single custom path', async ({ expect }) => {
const jobs = await fsImportAll(resolveFixture('app/contacts/jobs'), { ignoreMissingRoot: true })

const names = Object.values(jobs).map((v: any) => v.name)
expect(names).toContain('ContactJob')
expect(names).not.toContain('EmailJob')
})

test('merges jobs from multiple paths (modular architecture)', async ({ expect }) => {
const results = await Promise.all([
fsImportAll(resolveFixture('app/contacts/jobs'), { ignoreMissingRoot: true }),
fsImportAll(resolveFixture('app/reminders/jobs'), { ignoreMissingRoot: true }),
])
const jobs = Object.assign({}, ...results)

const names = Object.values(jobs).map((v: any) => v.name)
expect(names).toContain('ContactJob')
expect(names).toContain('ReminderJob')
expect(names).not.toContain('EmailJob')
})

test('returns empty object for a missing directory', async ({ expect }) => {
const jobs = await fsImportAll(resolveFixture('app/nonexistent/jobs'), { ignoreMissingRoot: true })

expect(Object.keys(jobs)).toHaveLength(0)
})
})
21 changes: 21 additions & 0 deletions tests/unit/resolve_job_paths.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { test } from '@japa/runner'
import { resolveJobPaths } from '../../jobs.js'

test.group('resolveJobPaths', () => {
test('returns ["app/jobs"] when no path is configured', ({ expect }) => {
expect(resolveJobPaths(undefined)).toEqual(['app/jobs'])
})

test('wraps a single string in an array', ({ expect }) => {
expect(resolveJobPaths('app/billing/jobs')).toEqual(['app/billing/jobs'])
})

test('returns an array of paths as-is', ({ expect }) => {
const paths = ['app/contacts/jobs', 'app/reminders/jobs']
expect(resolveJobPaths(paths)).toEqual(paths)
})

test('returns a one-element array unchanged', ({ expect }) => {
expect(resolveJobPaths(['app/custom/jobs'])).toEqual(['app/custom/jobs'])
})
})
5 changes: 5 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,10 @@
"rootDir": "./",
"outDir": "./build",
"resolveJsonModule": false
},
"ts-node": {
"swc": false,
"esm": true,
"transpileOnly": true
}
}