Skip to content

Commit 7b651d6

Browse files
committed
refactor: extract resolveAdapters utility and add regression tests
1 parent 1592098 commit 7b651d6

4 files changed

Lines changed: 208 additions & 25 deletions

File tree

commands/queue_work.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
import { flags, BaseCommand } from '@adonisjs/core/ace'
11+
import { resolveAdapters } from '../src/utils.js'
1112
import type { CommandOptions } from '@adonisjs/core/types/ace'
1213
import type { QueueConfig } from '../src/types/main.js'
1314

@@ -38,19 +39,7 @@ export default class QueueWork extends BaseCommand {
3839
const router = await this.app.container.make('router')
3940
router.commit()
4041

41-
/**
42-
* Resolve adapter factories from config providers
43-
*/
44-
const resolvedAdapters: Record<string, () => any> = {}
45-
46-
for (const [name, adapterConfig] of Object.entries(config.adapters)) {
47-
if (typeof adapterConfig === 'function') {
48-
resolvedAdapters[name] = adapterConfig as () => any
49-
} else {
50-
resolvedAdapters[name] = await adapterConfig.resolver(this.app)
51-
}
52-
}
53-
42+
const resolvedAdapters = await resolveAdapters(config, this.app)
5443
const queues = this.queue ? this.queue.split(',').map((q) => q.trim()) : ['default']
5544

5645
this.logger.info(`Starting worker for queues: ${queues.join(', ')}`)

providers/queue_provider.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
import '../src/types/extended.js'
11+
import { resolveAdapters } from '../src/utils.js'
1112
import type { ApplicationService } from '@adonisjs/core/types'
1213
import type { QueueConfig } from '../src/types/main.js'
1314

@@ -19,18 +20,7 @@ export default class QueueProvider {
1920
const { QueueManager } = await import('@boringnode/queue')
2021
const config = this.app.config.get<QueueConfig>('queue')
2122

22-
/**
23-
* Resolve adapter factories from config providers
24-
*/
25-
const resolvedAdapters: Record<string, () => any> = {}
26-
27-
for (const [name, adapterConfig] of Object.entries(config.adapters)) {
28-
if (typeof adapterConfig === 'function') {
29-
resolvedAdapters[name] = adapterConfig as () => any
30-
} else {
31-
resolvedAdapters[name] = await adapterConfig.resolver(this.app)
32-
}
33-
}
23+
const resolvedAdapters = await resolveAdapters(config, this.app)
3424

3525
/**
3626
* Inject jobFactory if not already defined.

src/utils.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* @adonisjs/queue
3+
*
4+
* (c) AdonisJS
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
import type { ApplicationService } from '@adonisjs/core/types'
11+
import type { QueueConfig } from './types/main.js'
12+
13+
type AdapterFactory = () => any
14+
15+
/**
16+
* Resolve adapter factories from config providers.
17+
*
18+
* Adapters in the config can be either:
19+
* - Direct factory functions
20+
* - ConfigProvider objects that need to be resolved
21+
*
22+
* This function normalizes them all to factory functions.
23+
*/
24+
export async function resolveAdapters(
25+
config: QueueConfig,
26+
app: ApplicationService
27+
): Promise<Record<string, AdapterFactory>> {
28+
const resolvedAdapters: Record<string, AdapterFactory> = {}
29+
30+
for (const [name, adapterConfig] of Object.entries(config.adapters)) {
31+
if (typeof adapterConfig === 'function') {
32+
resolvedAdapters[name] = adapterConfig as AdapterFactory
33+
} else {
34+
resolvedAdapters[name] = await adapterConfig.resolver(app)
35+
}
36+
}
37+
38+
return resolvedAdapters
39+
}

tests/utils.spec.ts

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
* @adonisjs/queue
3+
*
4+
* (c) AdonisJS
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
import { test } from '@japa/runner'
11+
import { IgnitorFactory } from '@adonisjs/core/factories'
12+
13+
import { defineConfig, drivers } from '../index.js'
14+
import { resolveAdapters } from '../src/utils.js'
15+
import type { QueueConfig } from '../src/types/main.js'
16+
17+
const BASE_URL = new URL('./tmp/', import.meta.url)
18+
19+
test.group('resolveAdapters', () => {
20+
test('should resolve config providers to adapter factories', async ({ assert }) => {
21+
const ignitor = new IgnitorFactory()
22+
.withCoreProviders()
23+
.withCoreConfig()
24+
.merge({
25+
config: {
26+
queue: defineConfig({
27+
default: 'sync',
28+
adapters: {
29+
sync: drivers.sync(),
30+
},
31+
}),
32+
},
33+
rcFileContents: {
34+
providers: [() => import('../providers/queue_provider.js')],
35+
},
36+
})
37+
.create(BASE_URL, {
38+
importer: (filePath) => {
39+
if (filePath.startsWith('./') || filePath.startsWith('../')) {
40+
return import(new URL(filePath, BASE_URL).href)
41+
}
42+
43+
return import(filePath)
44+
},
45+
})
46+
47+
const app = ignitor.createApp('console')
48+
await app.init().then(() => app.boot())
49+
50+
const config = app.config.get<QueueConfig>('queue')
51+
52+
/**
53+
* Before resolution, the adapter is a ConfigProvider (object with resolver method)
54+
*/
55+
assert.isObject(config.adapters.sync)
56+
assert.isFunction((config.adapters.sync as any).resolver)
57+
58+
/**
59+
* After resolution, the adapter should be a factory function
60+
*/
61+
const resolvedAdapters = await resolveAdapters(config, app)
62+
assert.isFunction(resolvedAdapters.sync)
63+
64+
await app.terminate()
65+
})
66+
67+
test('should pass through direct factory functions unchanged', async ({ assert }) => {
68+
const directFactory = () => ({
69+
pushOn: async () => {},
70+
pushLaterOn: async () => {},
71+
pop: async () => null,
72+
acknowledge: async () => {},
73+
fail: async () => {},
74+
getFailedJobs: async () => [],
75+
removeFailedJob: async () => {},
76+
clearFailedJobs: async () => {},
77+
destroy: async () => {},
78+
})
79+
80+
const ignitor = new IgnitorFactory()
81+
.withCoreProviders()
82+
.withCoreConfig()
83+
.merge({
84+
config: {
85+
queue: defineConfig({
86+
default: 'custom',
87+
adapters: {
88+
custom: directFactory as any,
89+
},
90+
}),
91+
},
92+
rcFileContents: {
93+
providers: [() => import('../providers/queue_provider.js')],
94+
},
95+
})
96+
.create(BASE_URL, {
97+
importer: (filePath) => {
98+
if (filePath.startsWith('./') || filePath.startsWith('../')) {
99+
return import(new URL(filePath, BASE_URL).href)
100+
}
101+
102+
return import(filePath)
103+
},
104+
})
105+
106+
const app = ignitor.createApp('console')
107+
await app.init().then(() => app.boot())
108+
109+
const config = app.config.get<QueueConfig>('queue')
110+
const resolvedAdapters = await resolveAdapters(config, app)
111+
112+
assert.strictEqual(resolvedAdapters.custom, directFactory)
113+
114+
await app.terminate()
115+
})
116+
117+
test('worker should accept resolved adapters without throwing', async ({ assert }) => {
118+
const ignitor = new IgnitorFactory()
119+
.withCoreProviders()
120+
.withCoreConfig()
121+
.merge({
122+
config: {
123+
queue: defineConfig({
124+
default: 'sync',
125+
adapters: {
126+
sync: drivers.sync(),
127+
},
128+
}),
129+
},
130+
rcFileContents: {
131+
providers: [() => import('../providers/queue_provider.js')],
132+
},
133+
})
134+
.create(BASE_URL, {
135+
importer: (filePath) => {
136+
if (filePath.startsWith('./') || filePath.startsWith('../')) {
137+
return import(new URL(filePath, BASE_URL).href)
138+
}
139+
140+
return import(filePath)
141+
},
142+
})
143+
144+
const app = ignitor.createApp('console')
145+
await app.init().then(() => app.boot())
146+
147+
const config = app.config.get<QueueConfig>('queue')
148+
const resolvedAdapters = await resolveAdapters(config, app)
149+
150+
/**
151+
* Creating a Worker with resolved adapters should not throw
152+
* "Adapter must be a factory function" error
153+
*/
154+
const { Worker } = await import('@boringnode/queue')
155+
156+
assert.doesNotThrow(() => {
157+
new Worker({
158+
...config,
159+
adapters: resolvedAdapters,
160+
})
161+
})
162+
163+
await app.terminate()
164+
})
165+
})

0 commit comments

Comments
 (0)