Skip to content

Commit 751a8bc

Browse files
committed
test refactors
1 parent 1633f44 commit 751a8bc

32 files changed

+3785
-3588
lines changed
Lines changed: 43 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Injector } from '@furystack/inject'
2-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
2+
import { usingAsync } from '@furystack/utils'
3+
import { describe, expect, it, vi } from 'vitest'
34

45
import { InstallApiClient } from './api-clients/install-api-client.js'
56
import { InstallService } from './install-service.js'
@@ -8,42 +9,46 @@ const createMockApiClient = () => ({
89
call: vi.fn().mockResolvedValue({ result: { state: 'needsInstall' } }),
910
})
1011

12+
const createSetup = (injector: Injector) => {
13+
const mockApi = createMockApiClient()
14+
injector.setExplicitInstance(mockApi as unknown as InstallApiClient, InstallApiClient)
15+
const service = injector.getInstance(InstallService)
16+
return { mockApi, service }
17+
}
18+
1119
describe('InstallService', () => {
12-
let injector: Injector
13-
let mockApi: ReturnType<typeof createMockApiClient>
14-
let service: InstallService
15-
16-
beforeEach(() => {
17-
injector = new Injector()
18-
mockApi = createMockApiClient()
19-
injector.setExplicitInstance(mockApi as unknown as InstallApiClient, InstallApiClient)
20-
service = injector.getInstance(InstallService)
21-
})
22-
23-
afterEach(() => {
24-
service[Symbol.dispose]()
25-
})
26-
27-
it('should fetch service status from the API', async () => {
28-
const result = await service.getServiceStatus()
29-
expect(result).toEqual({ state: 'needsInstall' })
30-
expect(mockApi.call).toHaveBeenCalledWith({
31-
method: 'GET',
32-
action: '/serviceStatus',
33-
})
34-
})
35-
36-
it('should cache the result on subsequent calls', async () => {
37-
await service.getServiceStatus()
38-
await service.getServiceStatus()
39-
expect(mockApi.call).toHaveBeenCalledTimes(1)
40-
})
41-
42-
it('should expose getServiceStatusAsObservable', () => {
43-
expect(typeof service.getServiceStatusAsObservable).toBe('function')
44-
})
45-
46-
it('should dispose without throwing', () => {
47-
expect(() => service[Symbol.dispose]()).not.toThrow()
48-
})
20+
it('should fetch service status from the API', () =>
21+
usingAsync(new Injector(), async (injector) => {
22+
const { mockApi, service } = createSetup(injector)
23+
24+
const result = await service.getServiceStatus()
25+
expect(result).toEqual({ state: 'needsInstall' })
26+
expect(mockApi.call).toHaveBeenCalledWith({
27+
method: 'GET',
28+
action: '/serviceStatus',
29+
})
30+
}))
31+
32+
it('should cache the result on subsequent calls', () =>
33+
usingAsync(new Injector(), async (injector) => {
34+
const { mockApi, service } = createSetup(injector)
35+
36+
await service.getServiceStatus()
37+
await service.getServiceStatus()
38+
expect(mockApi.call).toHaveBeenCalledTimes(1)
39+
}))
40+
41+
it('should expose getServiceStatusAsObservable', () =>
42+
usingAsync(new Injector(), async (injector) => {
43+
const { service } = createSetup(injector)
44+
45+
expect(typeof service.getServiceStatusAsObservable).toBe('function')
46+
}))
47+
48+
it('should dispose without throwing', () =>
49+
usingAsync(new Injector(), async (injector) => {
50+
const { service } = createSetup(injector)
51+
52+
expect(() => service[Symbol.dispose]()).not.toThrow()
53+
}))
4954
})

service/src/app-models/github-repositories/actions/validate-repo-action.spec.ts

Lines changed: 77 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { addStore, InMemoryStore, useSystemIdentityContext } from '@furystack/co
22
import { Injector } from '@furystack/inject'
33
import { useLogging, VerboseConsoleLogger } from '@furystack/logging'
44
import { getRepository } from '@furystack/repository'
5+
import { usingAsync } from '@furystack/utils'
56
import { GitHubRepository } from 'common'
6-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
7+
import { beforeEach, describe, expect, it, vi } from 'vitest'
78

89
import { ValidateRepoAction } from './validate-repo-action.js'
910

@@ -28,82 +29,90 @@ const createMockActionContext = (options: { injector: Injector; urlParams?: Reco
2829
response: {} as never,
2930
})
3031

31-
describe('ValidateRepoAction', () => {
32-
let injector: Injector
33-
let repoStore: InMemoryStore<GitHubRepository, 'id'>
32+
const createSetup = () => {
33+
const injector = new Injector()
34+
useLogging(injector, VerboseConsoleLogger)
35+
const repoStore = new InMemoryStore({ model: GitHubRepository, primaryKey: 'id' })
36+
addStore(injector, repoStore)
37+
getRepository(injector).createDataSet(GitHubRepository, 'id', {})
38+
return { injector, repoStore }
39+
}
3440

41+
describe('ValidateRepoAction', () => {
3542
beforeEach(() => {
36-
injector = new Injector()
37-
useLogging(injector, VerboseConsoleLogger)
38-
39-
repoStore = new InMemoryStore({ model: GitHubRepository, primaryKey: 'id' })
40-
addStore(injector, repoStore)
41-
getRepository(injector).createDataSet(GitHubRepository, 'id', {})
42-
4343
vi.clearAllMocks()
4444
})
4545

46-
afterEach(async () => {
47-
await injector[Symbol.asyncDispose]()
48-
})
49-
50-
it('should return accessible: true when git ls-remote succeeds', async () => {
51-
const ts = new Date().toISOString()
52-
await repoStore.add({
53-
id: 'repo-1',
54-
stackName: 'test-stack',
55-
url: 'https://github.com/user/repo.git',
56-
displayName: 'Test Repo',
57-
description: '',
58-
createdAt: ts,
59-
updatedAt: ts,
60-
} as GitHubRepository)
61-
62-
execFileMock.mockResolvedValue({ stdout: 'abc123\tHEAD\n', stderr: '' })
63-
64-
const elevated = useSystemIdentityContext({ injector })
65-
const result = await ValidateRepoAction(
66-
createMockActionContext({ injector: elevated, urlParams: { id: 'repo-1' } }),
67-
)
68-
await elevated[Symbol.asyncDispose]()
69-
70-
const body = result.chunk as { accessible: boolean }
71-
expect(body.accessible).toBe(true)
72-
expect(execFileMock).toHaveBeenCalledWith('git', ['ls-remote', '--exit-code', 'https://github.com/user/repo.git'], {
73-
timeout: 15000,
46+
it('should return accessible: true when git ls-remote succeeds', () => {
47+
const { injector, repoStore } = createSetup()
48+
return usingAsync(injector, async () => {
49+
const ts = new Date().toISOString()
50+
await repoStore.add({
51+
id: 'repo-1',
52+
stackName: 'test-stack',
53+
url: 'https://github.com/user/repo.git',
54+
displayName: 'Test Repo',
55+
description: '',
56+
createdAt: ts,
57+
updatedAt: ts,
58+
} as GitHubRepository)
59+
60+
execFileMock.mockResolvedValue({ stdout: 'abc123\tHEAD\n', stderr: '' })
61+
62+
const elevated = useSystemIdentityContext({ injector })
63+
const result = await ValidateRepoAction(
64+
createMockActionContext({ injector: elevated, urlParams: { id: 'repo-1' } }),
65+
)
66+
await elevated[Symbol.asyncDispose]()
67+
68+
const body = result.chunk as { accessible: boolean }
69+
expect(body.accessible).toBe(true)
70+
expect(execFileMock).toHaveBeenCalledWith(
71+
'git',
72+
['ls-remote', '--exit-code', 'https://github.com/user/repo.git'],
73+
{
74+
timeout: 15000,
75+
},
76+
)
7477
})
7578
})
7679

77-
it('should return accessible: false when git ls-remote fails', async () => {
78-
const ts = new Date().toISOString()
79-
await repoStore.add({
80-
id: 'repo-2',
81-
stackName: 'test-stack',
82-
url: 'https://github.com/user/bad-repo.git',
83-
displayName: 'Bad Repo',
84-
description: '',
85-
createdAt: ts,
86-
updatedAt: ts,
87-
} as GitHubRepository)
88-
89-
execFileMock.mockRejectedValue(new Error('Repository not found'))
90-
91-
const elevated = useSystemIdentityContext({ injector })
92-
const result = await ValidateRepoAction(
93-
createMockActionContext({ injector: elevated, urlParams: { id: 'repo-2' } }),
94-
)
95-
await elevated[Symbol.asyncDispose]()
96-
97-
const body = result.chunk as { accessible: boolean; message?: string }
98-
expect(body.accessible).toBe(false)
99-
expect(body.message).toContain('Repository not found')
80+
it('should return accessible: false when git ls-remote fails', () => {
81+
const { injector, repoStore } = createSetup()
82+
return usingAsync(injector, async () => {
83+
const ts = new Date().toISOString()
84+
await repoStore.add({
85+
id: 'repo-2',
86+
stackName: 'test-stack',
87+
url: 'https://github.com/user/bad-repo.git',
88+
displayName: 'Bad Repo',
89+
description: '',
90+
createdAt: ts,
91+
updatedAt: ts,
92+
} as GitHubRepository)
93+
94+
execFileMock.mockRejectedValue(new Error('Repository not found'))
95+
96+
const elevated = useSystemIdentityContext({ injector })
97+
const result = await ValidateRepoAction(
98+
createMockActionContext({ injector: elevated, urlParams: { id: 'repo-2' } }),
99+
)
100+
await elevated[Symbol.asyncDispose]()
101+
102+
const body = result.chunk as { accessible: boolean; message?: string }
103+
expect(body.accessible).toBe(false)
104+
expect(body.message).toContain('Repository not found')
105+
})
100106
})
101107

102-
it('should throw 404 when repository does not exist', async () => {
103-
const elevated = useSystemIdentityContext({ injector })
104-
await expect(
105-
ValidateRepoAction(createMockActionContext({ injector: elevated, urlParams: { id: 'nonexistent' } })),
106-
).rejects.toThrow('Repository not found')
107-
await elevated[Symbol.asyncDispose]()
108+
it('should throw 404 when repository does not exist', () => {
109+
const { injector } = createSetup()
110+
return usingAsync(injector, async () => {
111+
const elevated = useSystemIdentityContext({ injector })
112+
await expect(
113+
ValidateRepoAction(createMockActionContext({ injector: elevated, urlParams: { id: 'nonexistent' } })),
114+
).rejects.toThrow('Repository not found')
115+
await elevated[Symbol.asyncDispose]()
116+
})
108117
})
109118
})

0 commit comments

Comments
 (0)