refactor(test): update vitest config and improve rclone service tests

- Replace vitest.workspace.js with vitest.config.ts for better project configuration
- Add vitest dependency and update test script in package.json
- Improve RCloneApiService tests with better mocking and error handling
- Update test snapshots to include backupBase path
This commit is contained in:
Eli Bosley
2025-07-12 09:41:39 -04:00
parent 0d864fa948
commit 4c0b967164
13 changed files with 79 additions and 33 deletions

View File

@@ -18,7 +18,11 @@
"Bash(pnpm add:*)",
"Bash(npx tsc:*)",
"Bash(pnpm list:*)",
"Bash(rm:*)"
"Bash(rm:*)",
"Bash(pnpm --filter ./api test)",
"Bash(pnpm i:*)",
"Bash(pnpm:*)",
"Bash(corepack prepare:*)"
]
},
"enableAllProjectMcpServers": false

View File

@@ -1,5 +1,5 @@
[api]
version="4.8.0"
version="4.4.1"
extraOrigins="https://google.com,https://test.com"
[local]
sandbox="yes"

View File

@@ -72,15 +72,28 @@ describe('RCloneApiService', () => {
mockPRetry = vi.mocked(pRetry.default);
mockExistsSync = vi.mocked(existsSync);
mockGot.post = vi.fn().mockResolvedValue({ body: {} });
mockExeca.mockReturnValue({
on: vi.fn(),
kill: vi.fn(),
killed: false,
pid: 12345,
} as any);
mockGot.post = vi.fn().mockImplementation((url: string) => {
// Mock the core/pid call to indicate socket is running
if (url.includes('core/pid')) {
return Promise.resolve({ body: { pid: 12345 } });
}
return Promise.resolve({ body: {} });
});
// Mock execa to return a resolved promise for rclone version check
mockExeca.mockImplementation((cmd: string, args: string[]) => {
if (cmd === 'rclone' && args[0] === 'version') {
return Promise.resolve({ stdout: 'rclone v1.67.0', stderr: '', exitCode: 0 } as any);
}
return {
on: vi.fn(),
kill: vi.fn(),
killed: false,
pid: 12345,
} as any;
});
mockPRetry.mockResolvedValue(undefined);
mockExistsSync.mockReturnValue(false);
// Mock socket exists
mockExistsSync.mockReturnValue(true);
mockFormatService = {
formatBytes: vi.fn(),
@@ -116,7 +129,10 @@ describe('RCloneApiService', () => {
};
service = new RCloneApiService(mockStatusService);
await service.onModuleInit();
// Mock the service as initialized without actually running onModuleInit
// to avoid the initialization API calls
(service as any).initialized = true;
(service as any).rcloneBaseUrl = 'http://unix:/tmp/rclone.sock:';
});
describe('getProviders', () => {
@@ -284,6 +300,9 @@ describe('RCloneApiService', () => {
options: { delete_on: 'dst' },
};
const mockResponse = { jobid: 'job-123' };
// Clear previous mock calls and set up fresh mock
mockGot.post.mockClear();
mockGot.post.mockResolvedValue({ body: mockResponse });
const result = await service.startBackup(input);
@@ -292,11 +311,11 @@ describe('RCloneApiService', () => {
expect(mockGot.post).toHaveBeenCalledWith(
'http://unix:/tmp/rclone.sock:/sync/copy',
expect.objectContaining({
json: {
json: expect.objectContaining({
srcFs: '/source/path',
dstFs: 'remote:backup/path',
delete_on: 'dst',
},
}),
})
);
});
@@ -305,8 +324,22 @@ describe('RCloneApiService', () => {
describe('getJobStatus', () => {
it('should return job status', async () => {
const input: GetRCloneJobStatusDto = { jobId: 'job-123' };
const mockStatus = { status: 'running', progress: 0.5 };
mockGot.post.mockResolvedValue({ body: mockStatus });
const mockStatus = { id: 'job-123', status: 'running', progress: 0.5 };
mockGot.post.mockImplementation((url: string) => {
if (url.includes('core/stats')) {
return Promise.resolve({ body: {} });
}
if (url.includes('job/status')) {
return Promise.resolve({ body: mockStatus });
}
return Promise.resolve({ body: {} });
});
// Mock the status service methods
const mockStatusService = (service as any).statusService;
mockStatusService.enhanceStatsWithFormattedFields = vi.fn().mockReturnValue({});
mockStatusService.transformStatsToJob = vi.fn().mockReturnValue(null);
mockStatusService.parseJobWithStats = vi.fn().mockReturnValue(mockStatus);
const result = await service.getJobStatus(input);
@@ -371,7 +404,7 @@ describe('RCloneApiService', () => {
mockGot.post.mockRejectedValue(httpError);
await expect(service.getProviders()).rejects.toThrow(
'Rclone API Error (config/providers, HTTP 404): Failed to process error response body. Raw body:'
'Rclone API Error (config/providers, HTTP 404): Failed to process error response: '
);
});
@@ -388,7 +421,7 @@ describe('RCloneApiService', () => {
mockGot.post.mockRejectedValue(httpError);
await expect(service.getProviders()).rejects.toThrow(
'Rclone API Error (config/providers, HTTP 400): Failed to process error response body. Raw body: invalid json'
'Rclone API Error (config/providers, HTTP 400): Failed to process error response: invalid json'
);
});
@@ -403,7 +436,7 @@ describe('RCloneApiService', () => {
mockGot.post.mockRejectedValue('unknown error');
await expect(service.getProviders()).rejects.toThrow(
'Unknown error calling RClone API (config/providers) with params {}: unknown error'
'Unknown error calling RClone API (config/providers): unknown error'
);
});
});

View File

@@ -31,6 +31,7 @@ exports[`Returns paths 1`] = `
"activationBase",
"webGuiBase",
"identConfig",
"backupBase",
"activation",
"boot",
"webgui",

View File

@@ -7,7 +7,7 @@
"build:watch": " pnpm -r --parallel build:watch",
"dev": "pnpm -r dev",
"unraid:deploy": "pnpm -r unraid:deploy",
"test": "pnpm -r test",
"test": "vitest",
"lint": "pnpm -r lint",
"lint:fix": "pnpm -r lint:fix",
"type-check": "pnpm -r type-check",
@@ -43,7 +43,8 @@
"@manypkg/cli": "0.24.0",
"chalk": "5.4.1",
"diff": "8.0.2",
"ignore": "7.0.5"
"ignore": "7.0.5",
"vitest": "3.2.4"
},
"devDependencies": {
"lint-staged": "16.1.2",

3
pnpm-lock.yaml generated
View File

@@ -20,6 +20,9 @@ importers:
ignore:
specifier: 7.0.5
version: 7.0.5
vitest:
specifier: 3.2.4
version: 3.2.4(@types/node@22.16.3)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0)(stylus@0.57.0)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)
devDependencies:
lint-staged:
specifier: 16.1.2

12
vitest.config.ts Normal file
View File

@@ -0,0 +1,12 @@
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
projects: [
"./api/vite.config.ts",
"./plugin/vitest.config.ts",
"./unraid-ui/vitest.config.ts",
"./web/vitest.config.mjs"
]
}
})

View File

@@ -1,8 +0,0 @@
import { defineWorkspace } from 'vitest/config'
export default defineWorkspace([
"./plugin/vitest.config.ts",
"./api/vite.config.ts",
"./web/vitest.config.mjs",
"./unraid-ui/vitest.config.ts"
])