mirror of
https://github.com/unraid/api.git
synced 2026-01-02 22:50:02 -06:00
Refactor RestService tests and remove deprecated files
- Updated `rest.service.spec.ts` to include new tests for `getCustomizationPath` and `getCustomizationStream` methods, ensuring proper handling of banner and case paths. - Removed obsolete test files: `rest.service.test.ts`, `rest-module-dependencies.test.ts`, and `rest-module.integration.test.ts` to streamline the test suite. - Added necessary mocks for file system operations and image file helper functions to enhance test reliability.
This commit is contained in:
@@ -1,52 +0,0 @@
|
||||
import { Test } from '@nestjs/testing';
|
||||
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { ApiReportService } from '@app/unraid-api/cli/api-report.service.js';
|
||||
import { RestService } from '@app/unraid-api/rest/rest.service.js';
|
||||
|
||||
// Mock external dependencies
|
||||
vi.mock('@app/store/index.js', () => ({
|
||||
getters: {
|
||||
paths: vi.fn(() => ({
|
||||
'log-base': '/tmp/logs',
|
||||
})),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('execa', () => ({
|
||||
execa: vi.fn().mockResolvedValue({ stdout: 'mocked output' }),
|
||||
}));
|
||||
|
||||
describe('RestService Dependencies', () => {
|
||||
it('should resolve ApiReportService dependency successfully', async () => {
|
||||
const mockApiReportService = {
|
||||
generateReport: vi.fn().mockResolvedValue({ timestamp: new Date().toISOString() }),
|
||||
};
|
||||
|
||||
const module = await Test.createTestingModule({
|
||||
providers: [
|
||||
RestService,
|
||||
{
|
||||
provide: ApiReportService,
|
||||
useValue: mockApiReportService,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
const restService = module.get<RestService>(RestService);
|
||||
expect(restService).toBeDefined();
|
||||
expect(restService).toBeInstanceOf(RestService);
|
||||
|
||||
await module.close();
|
||||
});
|
||||
|
||||
it('should fail gracefully when ApiReportService is missing', async () => {
|
||||
// This test ensures we get a clear error when dependencies are missing
|
||||
await expect(
|
||||
Test.createTestingModule({
|
||||
providers: [RestService],
|
||||
}).compile()
|
||||
).rejects.toThrow(/ApiReportService/);
|
||||
});
|
||||
});
|
||||
@@ -1,84 +0,0 @@
|
||||
import { CacheModule } from '@nestjs/cache-manager';
|
||||
import { Test } from '@nestjs/testing';
|
||||
|
||||
import { CANONICAL_INTERNAL_CLIENT_TOKEN } from '@unraid/shared';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { ApiReportService } from '@app/unraid-api/cli/api-report.service.js';
|
||||
import { LogService } from '@app/unraid-api/cli/log.service.js';
|
||||
import { RestModule } from '@app/unraid-api/rest/rest.module.js';
|
||||
import { RestService } from '@app/unraid-api/rest/rest.service.js';
|
||||
|
||||
// Mock external dependencies that cause issues in tests
|
||||
vi.mock('@app/store/index.js', () => ({
|
||||
store: {
|
||||
getState: vi.fn(() => ({
|
||||
paths: {
|
||||
'log-base': '/tmp/logs',
|
||||
'auth-keys': '/tmp/auth-keys',
|
||||
config: '/tmp/config',
|
||||
},
|
||||
emhttp: {},
|
||||
dynamix: { notify: { path: '/tmp/notifications' } },
|
||||
registration: {},
|
||||
})),
|
||||
subscribe: vi.fn(() => vi.fn()), // Return unsubscribe function
|
||||
},
|
||||
getters: {
|
||||
paths: vi.fn(() => ({
|
||||
'log-base': '/tmp/logs',
|
||||
'auth-keys': '/tmp/auth-keys',
|
||||
config: '/tmp/config',
|
||||
})),
|
||||
dynamix: vi.fn(() => ({ notify: { path: '/tmp/notifications' } })),
|
||||
emhttp: vi.fn(() => ({})),
|
||||
registration: vi.fn(() => ({})),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@app/core/log.js', () => ({
|
||||
levels: ['trace', 'debug', 'info', 'warn', 'error', 'fatal'],
|
||||
apiLogger: {
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
},
|
||||
pluginLogger: {
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
trace: vi.fn(),
|
||||
fatal: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('execa', () => ({
|
||||
execa: vi.fn().mockResolvedValue({ stdout: 'mocked output' }),
|
||||
}));
|
||||
|
||||
describe('RestModule Integration', () => {
|
||||
it('should compile with RestService having access to ApiReportService', async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [CacheModule.register({ isGlobal: true }), RestModule],
|
||||
})
|
||||
// Override services that have complex dependencies for testing
|
||||
.overrideProvider(CANONICAL_INTERNAL_CLIENT_TOKEN)
|
||||
.useValue({ getClient: vi.fn() })
|
||||
.overrideProvider(LogService)
|
||||
.useValue({ error: vi.fn(), debug: vi.fn() })
|
||||
.compile();
|
||||
|
||||
const restService = module.get<RestService>(RestService);
|
||||
const apiReportService = module.get<ApiReportService>(ApiReportService);
|
||||
|
||||
expect(restService).toBeDefined();
|
||||
expect(apiReportService).toBeDefined();
|
||||
|
||||
// Verify RestService has the injected ApiReportService
|
||||
expect(restService['apiReportService']).toBeDefined();
|
||||
|
||||
await module.close();
|
||||
}, 10000);
|
||||
});
|
||||
@@ -1,132 +0,0 @@
|
||||
import { Test } from '@nestjs/testing';
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { ApiReportService } from '@app/unraid-api/cli/api-report.service.js';
|
||||
import { RestService } from '@app/unraid-api/rest/rest.service.js';
|
||||
|
||||
const mockWriteFile = vi.fn();
|
||||
|
||||
vi.mock('node:fs/promises', () => ({
|
||||
writeFile: (...args: any[]) => mockWriteFile(...args),
|
||||
stat: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock ApiReportService
|
||||
const mockApiReportService = {
|
||||
generateReport: vi.fn(),
|
||||
};
|
||||
|
||||
describe('RestService', () => {
|
||||
let restService: RestService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
providers: [RestService, { provide: ApiReportService, useValue: mockApiReportService }],
|
||||
}).compile();
|
||||
|
||||
restService = module.get<RestService>(RestService);
|
||||
|
||||
// Clear mocks
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('saveApiReport', () => {
|
||||
it('should generate report using ApiReportService and save to file', async () => {
|
||||
const mockReport = {
|
||||
timestamp: '2023-01-01T00:00:00.000Z',
|
||||
connectionStatus: {
|
||||
running: 'yes' as const,
|
||||
},
|
||||
system: {
|
||||
id: 'test-uuid',
|
||||
name: 'Test Server',
|
||||
version: '6.12.0',
|
||||
machineId: 'REDACTED',
|
||||
manufacturer: 'Test Manufacturer',
|
||||
model: 'Test Model',
|
||||
},
|
||||
connect: {
|
||||
installed: true,
|
||||
dynamicRemoteAccess: {
|
||||
enabledType: 'STATIC',
|
||||
runningType: 'STATIC',
|
||||
error: null,
|
||||
},
|
||||
},
|
||||
config: {
|
||||
valid: true,
|
||||
error: null,
|
||||
},
|
||||
services: {
|
||||
cloud: { name: 'cloud', online: true },
|
||||
minigraph: { name: 'minigraph', online: false },
|
||||
allServices: [],
|
||||
},
|
||||
remote: {
|
||||
apikey: 'REDACTED',
|
||||
localApiKey: 'REDACTED',
|
||||
accesstoken: 'REDACTED',
|
||||
idtoken: 'REDACTED',
|
||||
refreshtoken: 'REDACTED',
|
||||
ssoSubIds: 'REDACTED',
|
||||
allowedOrigins: 'REDACTED',
|
||||
email: 'REDACTED',
|
||||
},
|
||||
};
|
||||
|
||||
const reportPath = '/tmp/test-report.json';
|
||||
mockApiReportService.generateReport.mockResolvedValue(mockReport);
|
||||
mockWriteFile.mockResolvedValue(undefined);
|
||||
|
||||
await restService.saveApiReport(reportPath);
|
||||
|
||||
// Verify ApiReportService was called (defaults to API running)
|
||||
expect(mockApiReportService.generateReport).toHaveBeenCalledWith();
|
||||
|
||||
// Verify file was written with correct content
|
||||
expect(mockWriteFile).toHaveBeenCalledWith(
|
||||
reportPath,
|
||||
JSON.stringify(mockReport, null, 2),
|
||||
'utf-8'
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle ApiReportService errors gracefully', async () => {
|
||||
const reportPath = '/tmp/test-report.json';
|
||||
const error = new Error('Report generation failed');
|
||||
mockApiReportService.generateReport.mockRejectedValue(error);
|
||||
|
||||
// Should not throw error
|
||||
await restService.saveApiReport(reportPath);
|
||||
|
||||
// Verify ApiReportService was called
|
||||
expect(mockApiReportService.generateReport).toHaveBeenCalled();
|
||||
|
||||
// Verify file write was not called due to error
|
||||
expect(mockWriteFile).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle file write errors gracefully', async () => {
|
||||
const mockReport = {
|
||||
timestamp: '2023-01-01T00:00:00.000Z',
|
||||
system: { name: 'Test' },
|
||||
};
|
||||
|
||||
const reportPath = '/tmp/test-report.json';
|
||||
mockApiReportService.generateReport.mockResolvedValue(mockReport);
|
||||
mockWriteFile.mockRejectedValue(new Error('File write failed'));
|
||||
|
||||
// Should not throw error
|
||||
await restService.saveApiReport(reportPath);
|
||||
|
||||
// Verify both service and file operations were attempted
|
||||
expect(mockApiReportService.generateReport).toHaveBeenCalled();
|
||||
expect(mockWriteFile).toHaveBeenCalledWith(
|
||||
reportPath,
|
||||
JSON.stringify(mockReport, null, 2),
|
||||
'utf-8'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Controller, Get, Logger, Param, Query, Req, Res, UnauthorizedException } from '@nestjs/common';
|
||||
import escapeHtml from 'escape-html';
|
||||
|
||||
import { AuthAction, Resource } from '@unraid/shared/graphql.model.js';
|
||||
import { UsePermissions } from '@unraid/shared/use-permissions.directive.js';
|
||||
import escapeHtml from 'escape-html';
|
||||
|
||||
import type { FastifyReply, FastifyRequest } from '@app/unraid-api/types/fastify.js';
|
||||
import { Public } from '@app/unraid-api/auth/public.decorator.js';
|
||||
|
||||
@@ -1,24 +1,88 @@
|
||||
import type { TestingModule } from '@nestjs/testing';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import type { ReadStream } from 'node:fs';
|
||||
import { createReadStream } from 'node:fs';
|
||||
import { Readable } from 'node:stream';
|
||||
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import {
|
||||
getBannerPathIfPresent,
|
||||
getCasePathIfPresent,
|
||||
} from '@app/core/utils/images/image-file-helpers.js';
|
||||
import { RestService } from '@app/unraid-api/rest/rest.service.js';
|
||||
|
||||
vi.mock('node:fs');
|
||||
vi.mock('@app/core/utils/images/image-file-helpers.js', () => ({
|
||||
getBannerPathIfPresent: vi.fn(),
|
||||
getCasePathIfPresent: vi.fn(),
|
||||
}));
|
||||
|
||||
describe('RestService', () => {
|
||||
let service: RestService;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
RestService,
|
||||
],
|
||||
providers: [RestService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<RestService>(RestService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
describe('getCustomizationPath', () => {
|
||||
it('returns banner path when present', async () => {
|
||||
const mockBannerPath = '/path/to/banner.png';
|
||||
vi.mocked(getBannerPathIfPresent).mockResolvedValue(mockBannerPath);
|
||||
|
||||
await expect(service.getCustomizationPath('banner')).resolves.toBe(mockBannerPath);
|
||||
});
|
||||
|
||||
it('returns case path when present', async () => {
|
||||
const mockCasePath = '/path/to/case.png';
|
||||
vi.mocked(getCasePathIfPresent).mockResolvedValue(mockCasePath);
|
||||
|
||||
await expect(service.getCustomizationPath('case')).resolves.toBe(mockCasePath);
|
||||
});
|
||||
|
||||
it('returns null when no path is available', async () => {
|
||||
vi.mocked(getBannerPathIfPresent).mockResolvedValue(null);
|
||||
vi.mocked(getCasePathIfPresent).mockResolvedValue(null);
|
||||
|
||||
await expect(service.getCustomizationPath('banner')).resolves.toBeNull();
|
||||
await expect(service.getCustomizationPath('case')).resolves.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCustomizationStream', () => {
|
||||
it('returns read stream for banner', async () => {
|
||||
const mockPath = '/path/to/banner.png';
|
||||
const mockStream: ReadStream = Readable.from([]) as ReadStream;
|
||||
|
||||
vi.mocked(getBannerPathIfPresent).mockResolvedValue(mockPath);
|
||||
vi.mocked(createReadStream).mockReturnValue(mockStream);
|
||||
|
||||
await expect(service.getCustomizationStream('banner')).resolves.toBe(mockStream);
|
||||
expect(createReadStream).toHaveBeenCalledWith(mockPath);
|
||||
});
|
||||
|
||||
it('returns read stream for case', async () => {
|
||||
const mockPath = '/path/to/case.png';
|
||||
const mockStream: ReadStream = Readable.from([]) as ReadStream;
|
||||
|
||||
vi.mocked(getCasePathIfPresent).mockResolvedValue(mockPath);
|
||||
vi.mocked(createReadStream).mockReturnValue(mockStream);
|
||||
|
||||
await expect(service.getCustomizationStream('case')).resolves.toBe(mockStream);
|
||||
expect(createReadStream).toHaveBeenCalledWith(mockPath);
|
||||
});
|
||||
|
||||
it('throws when no customization is available', async () => {
|
||||
vi.mocked(getBannerPathIfPresent).mockResolvedValue(null);
|
||||
vi.mocked(getCasePathIfPresent).mockResolvedValue(null);
|
||||
|
||||
await expect(service.getCustomizationStream('banner')).rejects.toThrow('No banner found');
|
||||
await expect(service.getCustomizationStream('case')).rejects.toThrow('No case found');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import type { ReadStream } from 'node:fs';
|
||||
import { createReadStream } from 'node:fs';
|
||||
import { Readable } from 'node:stream';
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { getBannerPathIfPresent, getCasePathIfPresent } from '@app/core/utils/images/image-file-helpers.js';
|
||||
import { RestService } from '@app/unraid-api/rest/rest.service.js';
|
||||
|
||||
vi.mock('node:fs');
|
||||
vi.mock('@app/core/utils/images/image-file-helpers.js', () => ({
|
||||
getBannerPathIfPresent: vi.fn(),
|
||||
getCasePathIfPresent: vi.fn(),
|
||||
}));
|
||||
|
||||
describe('RestService', () => {
|
||||
let service: RestService;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [RestService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<RestService>(RestService);
|
||||
});
|
||||
|
||||
describe('getCustomizationPath', () => {
|
||||
it('returns banner path when present', async () => {
|
||||
const mockBannerPath = '/path/to/banner.png';
|
||||
vi.mocked(getBannerPathIfPresent).mockResolvedValue(mockBannerPath);
|
||||
|
||||
await expect(service.getCustomizationPath('banner')).resolves.toBe(mockBannerPath);
|
||||
});
|
||||
|
||||
it('returns case path when present', async () => {
|
||||
const mockCasePath = '/path/to/case.png';
|
||||
vi.mocked(getCasePathIfPresent).mockResolvedValue(mockCasePath);
|
||||
|
||||
await expect(service.getCustomizationPath('case')).resolves.toBe(mockCasePath);
|
||||
});
|
||||
|
||||
it('returns null when no path is available', async () => {
|
||||
vi.mocked(getBannerPathIfPresent).mockResolvedValue(null);
|
||||
vi.mocked(getCasePathIfPresent).mockResolvedValue(null);
|
||||
|
||||
await expect(service.getCustomizationPath('banner')).resolves.toBeNull();
|
||||
await expect(service.getCustomizationPath('case')).resolves.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCustomizationStream', () => {
|
||||
it('returns read stream for banner', async () => {
|
||||
const mockPath = '/path/to/banner.png';
|
||||
const mockStream: ReadStream = Readable.from([]) as ReadStream;
|
||||
|
||||
vi.mocked(getBannerPathIfPresent).mockResolvedValue(mockPath);
|
||||
vi.mocked(createReadStream).mockReturnValue(mockStream);
|
||||
|
||||
await expect(service.getCustomizationStream('banner')).resolves.toBe(mockStream);
|
||||
expect(createReadStream).toHaveBeenCalledWith(mockPath);
|
||||
});
|
||||
|
||||
it('returns read stream for case', async () => {
|
||||
const mockPath = '/path/to/case.png';
|
||||
const mockStream: ReadStream = Readable.from([]) as ReadStream;
|
||||
|
||||
vi.mocked(getCasePathIfPresent).mockResolvedValue(mockPath);
|
||||
vi.mocked(createReadStream).mockReturnValue(mockStream);
|
||||
|
||||
await expect(service.getCustomizationStream('case')).resolves.toBe(mockStream);
|
||||
expect(createReadStream).toHaveBeenCalledWith(mockPath);
|
||||
});
|
||||
|
||||
it('throws when no customization is available', async () => {
|
||||
vi.mocked(getBannerPathIfPresent).mockResolvedValue(null);
|
||||
vi.mocked(getCasePathIfPresent).mockResolvedValue(null);
|
||||
|
||||
await expect(service.getCustomizationStream('banner')).rejects.toThrow('No banner found');
|
||||
await expect(service.getCustomizationStream('case')).rejects.toThrow('No case found');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,10 @@ import { Injectable } from '@nestjs/common';
|
||||
import type { ReadStream } from 'node:fs';
|
||||
import { createReadStream } from 'node:fs';
|
||||
|
||||
import { getBannerPathIfPresent, getCasePathIfPresent } from '@app/core/utils/images/image-file-helpers.js';
|
||||
import {
|
||||
getBannerPathIfPresent,
|
||||
getCasePathIfPresent,
|
||||
} from '@app/core/utils/images/image-file-helpers.js';
|
||||
|
||||
@Injectable()
|
||||
export class RestService {
|
||||
|
||||
@@ -134,7 +134,6 @@ describe('component-registry', () => {
|
||||
'user-profile',
|
||||
'auth',
|
||||
'connect-settings',
|
||||
'download-api-logs',
|
||||
'modals',
|
||||
'registration',
|
||||
'wan-ip-check',
|
||||
|
||||
Reference in New Issue
Block a user