-
Notifications
You must be signed in to change notification settings - Fork 18
feat: share internal boot state #1921
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,188 @@ | ||
| import type { Cache } from 'cache-manager'; | ||
| import { beforeEach, describe, expect, it, vi } from 'vitest'; | ||
|
|
||
| import { ArrayDiskType } from '@app/unraid-api/graph/resolvers/array/array.model.js'; | ||
| import { ArrayService } from '@app/unraid-api/graph/resolvers/array/array.service.js'; | ||
| import { DisksService } from '@app/unraid-api/graph/resolvers/disks/disks.service.js'; | ||
| import { InternalBootStateService } from '@app/unraid-api/graph/resolvers/disks/internal-boot-state.service.js'; | ||
|
|
||
| describe('InternalBootStateService', () => { | ||
| const cacheStore = new Map<string, unknown>(); | ||
| const arrayService = { | ||
| getArrayData: vi.fn(), | ||
| }; | ||
| const disksService = { | ||
| getInternalBootDevices: vi.fn(), | ||
| }; | ||
| const cacheManager = { | ||
| async get<T>(key: string): Promise<T | undefined> { | ||
| return cacheStore.get(key) as T | undefined; | ||
| }, | ||
| async set<T>(key: string, value: T): Promise<T> { | ||
| cacheStore.set(key, value); | ||
| return value; | ||
| }, | ||
| async del(key: string): Promise<boolean> { | ||
| return cacheStore.delete(key); | ||
| }, | ||
| }; | ||
| const getSpy = vi.spyOn(cacheManager, 'get'); | ||
| const setSpy = vi.spyOn(cacheManager, 'set'); | ||
| const delSpy = vi.spyOn(cacheManager, 'del'); | ||
|
|
||
| const createService = () => | ||
| new InternalBootStateService( | ||
| arrayService as unknown as ArrayService, | ||
| disksService as unknown as DisksService, | ||
| cacheManager as unknown as Cache | ||
| ); | ||
|
|
||
| beforeEach(() => { | ||
| cacheStore.clear(); | ||
| vi.clearAllMocks(); | ||
| }); | ||
|
|
||
| it('returns false without scanning disks when the system is not booted from flash', async () => { | ||
| const service = createService(); | ||
|
|
||
| const result = await service.getBootedFromFlashWithInternalBootSetupForBootDisk({ | ||
| type: ArrayDiskType.CACHE, | ||
| }); | ||
|
|
||
| expect(result).toBe(false); | ||
| expect(disksService.getInternalBootDevices).not.toHaveBeenCalled(); | ||
| expect(getSpy).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('caches the internal boot device lookup result', async () => { | ||
| disksService.getInternalBootDevices.mockResolvedValue([{ device: '/dev/nvme0n1' }]); | ||
| const service = createService(); | ||
|
|
||
| const firstResult = await service.getBootedFromFlashWithInternalBootSetupForBootDisk({ | ||
| type: ArrayDiskType.FLASH, | ||
| }); | ||
| const secondResult = await service.getBootedFromFlashWithInternalBootSetupForBootDisk({ | ||
| type: ArrayDiskType.FLASH, | ||
| }); | ||
|
|
||
| expect(firstResult).toBe(true); | ||
| expect(secondResult).toBe(true); | ||
| expect(disksService.getInternalBootDevices).toHaveBeenCalledTimes(1); | ||
| expect(setSpy).toHaveBeenCalledTimes(1); | ||
| }); | ||
|
|
||
| it('coalesces concurrent cache misses into a single disk scan', async () => { | ||
| let resolveLookup: ((value: Array<{ device: string }>) => void) | undefined; | ||
|
|
||
| disksService.getInternalBootDevices.mockImplementation( | ||
| () => | ||
| new Promise<Array<{ device: string }>>((resolve) => { | ||
| resolveLookup = resolve; | ||
| }) | ||
| ); | ||
| const service = createService(); | ||
|
|
||
| const firstLookup = service.getBootedFromFlashWithInternalBootSetupForBootDisk({ | ||
| type: ArrayDiskType.FLASH, | ||
| }); | ||
| const secondLookup = service.getBootedFromFlashWithInternalBootSetupForBootDisk({ | ||
| type: ArrayDiskType.FLASH, | ||
| }); | ||
|
|
||
| await Promise.resolve(); | ||
|
|
||
| expect(disksService.getInternalBootDevices).toHaveBeenCalledTimes(1); | ||
|
|
||
| resolveLookup?.([{ device: '/dev/nvme0n1' }]); | ||
|
|
||
| await expect(firstLookup).resolves.toBe(true); | ||
| await expect(secondLookup).resolves.toBe(true); | ||
| expect(setSpy).toHaveBeenCalledTimes(1); | ||
| }); | ||
|
|
||
| it('invalidates the cached lookup result when requested', async () => { | ||
| disksService.getInternalBootDevices | ||
| .mockResolvedValueOnce([{ device: '/dev/nvme0n1' }]) | ||
| .mockResolvedValueOnce([]); | ||
| const service = createService(); | ||
|
|
||
| const initialResult = await service.getBootedFromFlashWithInternalBootSetupForBootDisk({ | ||
| type: ArrayDiskType.FLASH, | ||
| }); | ||
|
|
||
| await service.invalidateCachedInternalBootDeviceState(); | ||
|
|
||
| const refreshedResult = await service.getBootedFromFlashWithInternalBootSetupForBootDisk({ | ||
| type: ArrayDiskType.FLASH, | ||
| }); | ||
|
|
||
| expect(initialResult).toBe(true); | ||
| expect(refreshedResult).toBe(false); | ||
| expect(disksService.getInternalBootDevices).toHaveBeenCalledTimes(2); | ||
| expect(delSpy).toHaveBeenCalledTimes(1); | ||
| }); | ||
|
|
||
| it('does not repopulate the cache with a stale in-flight lookup after invalidation', async () => { | ||
| let resolveFirstLookup: ((value: Array<{ device: string }>) => void) | undefined; | ||
| let resolveSecondLookup: ((value: Array<{ device: string }>) => void) | undefined; | ||
|
|
||
| disksService.getInternalBootDevices | ||
| .mockImplementationOnce( | ||
| () => | ||
| new Promise<Array<{ device: string }>>((resolve) => { | ||
| resolveFirstLookup = resolve; | ||
| }) | ||
| ) | ||
| .mockImplementationOnce( | ||
| () => | ||
| new Promise<Array<{ device: string }>>((resolve) => { | ||
| resolveSecondLookup = resolve; | ||
| }) | ||
| ); | ||
| const service = createService(); | ||
|
|
||
| const firstLookup = service.getBootedFromFlashWithInternalBootSetupForBootDisk({ | ||
| type: ArrayDiskType.FLASH, | ||
| }); | ||
|
|
||
| await Promise.resolve(); | ||
| expect(disksService.getInternalBootDevices).toHaveBeenCalledTimes(1); | ||
|
|
||
| await service.invalidateCachedInternalBootDeviceState(); | ||
| resolveFirstLookup?.([{ device: '/dev/nvme0n1' }]); | ||
|
|
||
| await expect(firstLookup).resolves.toBe(true); | ||
| expect(setSpy).not.toHaveBeenCalled(); | ||
|
|
||
| const secondLookup = service.getBootedFromFlashWithInternalBootSetupForBootDisk({ | ||
| type: ArrayDiskType.FLASH, | ||
| }); | ||
|
|
||
| await Promise.resolve(); | ||
| expect(disksService.getInternalBootDevices).toHaveBeenCalledTimes(2); | ||
|
|
||
| resolveSecondLookup?.([]); | ||
|
|
||
| await expect(secondLookup).resolves.toBe(false); | ||
| expect(setSpy).toHaveBeenCalledTimes(1); | ||
| expect(setSpy).toHaveBeenLastCalledWith( | ||
| 'internal-boot-state:has-internal-boot-devices', | ||
| false, | ||
| 10000 | ||
| ); | ||
| }); | ||
|
|
||
| it('uses array boot data for the shared top-level lookup', async () => { | ||
| arrayService.getArrayData.mockResolvedValue({ | ||
| boot: { | ||
| type: ArrayDiskType.FLASH, | ||
| }, | ||
| }); | ||
| disksService.getInternalBootDevices.mockResolvedValue([{ device: '/dev/nvme0n1' }]); | ||
| const service = createService(); | ||
|
|
||
| await expect(service.getBootedFromFlashWithInternalBootSetup()).resolves.toBe(true); | ||
| expect(arrayService.getArrayData).toHaveBeenCalledTimes(1); | ||
| expect(disksService.getInternalBootDevices).toHaveBeenCalledTimes(1); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,3 +1,5 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Logger } from '@nestjs/common'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { execa } from 'execa'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { beforeEach, describe, expect, it, vi } from 'vitest'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -28,11 +30,13 @@ vi.mock('@app/store/services/state-file-loader.js', () => ({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| describe('OnboardingInternalBootService', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const internalBootStateService = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| getBootedFromFlashWithInternalBootSetup: vi.fn(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| invalidateCachedInternalBootDeviceState: vi.fn(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| beforeEach(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| vi.clearAllMocks(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| internalBootStateService.getBootedFromFlashWithInternalBootSetup.mockResolvedValue(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| internalBootStateService.invalidateCachedInternalBootDeviceState.mockResolvedValue(undefined); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| vi.mocked(getters.emhttp).mockReturnValue({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| devices: [], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| disks: [], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -88,6 +92,9 @@ describe('OnboardingInternalBootService', () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { waitForToken: true } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(internalBootStateService.invalidateCachedInternalBootDeviceState).toHaveBeenCalledTimes( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it('runs efibootmgr update flow when updateBios is requested', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -139,6 +146,9 @@ describe('OnboardingInternalBootService', () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.ok).toBe(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.code).toBe(0); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.output).toContain('BIOS boot entry updates completed successfully.'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(internalBootStateService.invalidateCachedInternalBootDeviceState).toHaveBeenCalledTimes( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(vi.mocked(emcmd)).toHaveBeenCalledTimes(4); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(vi.mocked(loadStateFileSync)).not.toHaveBeenCalled(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(vi.mocked(execa)).toHaveBeenNthCalledWith(1, 'efibootmgr', [], { reject: false }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -209,6 +219,35 @@ describe('OnboardingInternalBootService', () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.output).toContain( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'BIOS boot entry updates completed with warnings; manual BIOS boot order changes may still be required.' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(internalBootStateService.invalidateCachedInternalBootDeviceState).toHaveBeenCalledTimes( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it('returns success when cache invalidation fails after setup and logs a warning', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const warnSpy = vi.spyOn(Logger.prototype, 'warn').mockImplementation(() => undefined); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| vi.mocked(emcmd).mockResolvedValue({ ok: true } as Awaited<ReturnType<typeof emcmd>>); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| internalBootStateService.invalidateCachedInternalBootDeviceState.mockRejectedValue( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| new Error('cache delete failed') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const service = createService(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const result = await service.createInternalBootPool({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| poolName: 'cache', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| devices: ['disk-1'], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bootSizeMiB: 16384, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updateBios: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.ok).toBe(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.code).toBe(0); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(vi.mocked(emcmd)).toHaveBeenCalledTimes(4); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(internalBootStateService.invalidateCachedInternalBootDeviceState).toHaveBeenCalledTimes( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(warnSpy).toHaveBeenCalledWith( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Failed to invalidate cached internal boot device state after successful setup: cache delete failed' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+227
to
251
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: In Vitest v3, To put a spied prototype method back to its original implementation/descriptor, use Sources: [1] [2] 🏁 Script executed: fd -t f "onboarding-internal-boot.service.spec.ts" -x cat -n {}Repository: unraid/api Length of output: 14868 Restore the Line 228 creates a prototype spy but never restores it. 🔧 Proposed fix it('returns success when cache invalidation fails after setup and logs a warning', async () => {
const warnSpy = vi.spyOn(Logger.prototype, 'warn').mockImplementation(() => undefined);
- vi.mocked(emcmd).mockResolvedValue({ ok: true } as Awaited<ReturnType<typeof emcmd>>);
- internalBootStateService.invalidateCachedInternalBootDeviceState.mockRejectedValue(
- new Error('cache delete failed')
- );
- const service = createService();
-
- const result = await service.createInternalBootPool({
- poolName: 'cache',
- devices: ['disk-1'],
- bootSizeMiB: 16384,
- updateBios: false,
- });
-
- expect(result.ok).toBe(true);
- expect(result.code).toBe(0);
- expect(vi.mocked(emcmd)).toHaveBeenCalledTimes(4);
- expect(internalBootStateService.invalidateCachedInternalBootDeviceState).toHaveBeenCalledTimes(
- 1
- );
- expect(warnSpy).toHaveBeenCalledWith(
- 'Failed to invalidate cached internal boot device state after successful setup: cache delete failed'
- );
+ try {
+ vi.mocked(emcmd).mockResolvedValue({ ok: true } as Awaited<ReturnType<typeof emcmd>>);
+ internalBootStateService.invalidateCachedInternalBootDeviceState.mockRejectedValue(
+ new Error('cache delete failed')
+ );
+ const service = createService();
+
+ const result = await service.createInternalBootPool({
+ poolName: 'cache',
+ devices: ['disk-1'],
+ bootSizeMiB: 16384,
+ updateBios: false,
+ });
+
+ expect(result.ok).toBe(true);
+ expect(result.code).toBe(0);
+ expect(vi.mocked(emcmd)).toHaveBeenCalledTimes(4);
+ expect(
+ internalBootStateService.invalidateCachedInternalBootDeviceState
+ ).toHaveBeenCalledTimes(1);
+ expect(warnSpy).toHaveBeenCalledWith(
+ 'Failed to invalidate cached internal boot device state after successful setup: cache delete failed'
+ );
+ } finally {
+ warnSpy.mockRestore();
+ }
});📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it('returns validation error for duplicate devices', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -226,6 +265,7 @@ describe('OnboardingInternalBootService', () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| code: 2, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| output: 'mkbootpool: duplicate device id: disk-1', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(internalBootStateService.invalidateCachedInternalBootDeviceState).not.toHaveBeenCalled(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(vi.mocked(emcmd)).not.toHaveBeenCalled(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -245,6 +285,7 @@ describe('OnboardingInternalBootService', () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| code: 3, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.output).toContain('internal boot is already configured'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(internalBootStateService.invalidateCachedInternalBootDeviceState).not.toHaveBeenCalled(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(vi.mocked(emcmd)).not.toHaveBeenCalled(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -265,6 +306,7 @@ describe('OnboardingInternalBootService', () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.code).toBe(1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.output).toContain('mkbootpool: command failed or timed out'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.output).toContain('state lookup failed'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(internalBootStateService.invalidateCachedInternalBootDeviceState).not.toHaveBeenCalled(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(vi.mocked(emcmd)).not.toHaveBeenCalled(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -283,5 +325,6 @@ describe('OnboardingInternalBootService', () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.code).toBe(1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.output).toContain('mkbootpool: command failed or timed out'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result.output).toContain('socket failure'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(internalBootStateService.invalidateCachedInternalBootDeviceState).not.toHaveBeenCalled(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.