test(server): speed up slow unit tests

This commit is contained in:
neon
2026-04-02 08:05:00 -06:00
parent c3fd3f8f2e
commit 908ccce549
9 changed files with 60 additions and 7 deletions
@@ -1,5 +1,10 @@
import { BadRequestException, ConflictException, ForbiddenException, ServiceUnavailableException, UnauthorizedException } from '@nestjs/common';
vi.mock('bcryptjs', () => ({
hash: vi.fn((value: string) => Promise.resolve(`mock-hash:${value}`)),
compare: vi.fn((plain: string, hashed: string) => Promise.resolve(hashed === `mock-hash:${plain}`)),
}));
import { AuthService } from './auth.service';
function makeDb(overrides?: Record<string, unknown>) {
@@ -1,4 +1,4 @@
import { ForbiddenException, NotFoundException } from '@nestjs/common';
import { ForbiddenException, Logger, NotFoundException } from '@nestjs/common';
import { MetadataCandidate, MetadataProviderKey } from '@projectx/types';
import type { Mocked } from 'vitest';
import { firstValueFrom, toArray } from 'rxjs';
@@ -39,6 +39,11 @@ describe('MetadataFetchService', () => {
let service: MetadataFetchService;
beforeEach(() => {
vi.spyOn(Logger.prototype, 'log').mockImplementation(() => undefined);
vi.spyOn(Logger.prototype, 'warn').mockImplementation(() => undefined);
vi.spyOn(Logger.prototype, 'debug').mockImplementation(() => undefined);
vi.spyOn(Logger.prototype, 'error').mockImplementation(() => undefined);
registry = {
all: vi.fn(),
select: vi.fn(),
@@ -164,6 +164,8 @@ describe('GoodreadsProvider', () => {
});
it('should use slug-based scoring in extractBookIds', async () => {
const BETWEEN_REQUESTS_MS = 600;
vi.useFakeTimers();
const searchHtml = `
<a href="/book/show/1.The_Great_Gatsby?from_srp=true">B1</a>
<a href="/book/show/2.Something_Else?from_srp=true">B2</a>
@@ -179,7 +181,14 @@ describe('GoodreadsProvider', () => {
.mockResolvedValueOnce({ ok: true, text: () => Promise.resolve(searchHtml) })
.mockResolvedValue({ ok: true, text: () => Promise.resolve(bookHtml) });
await provider.search({ title: 'The Great Gatsby' });
const searchPromise = provider.search({ title: 'The Great Gatsby' });
await vi.advanceTimersByTimeAsync(0);
await vi.advanceTimersByTimeAsync(0);
await vi.advanceTimersByTimeAsync(BETWEEN_REQUESTS_MS);
await vi.advanceTimersByTimeAsync(0);
await vi.advanceTimersByTimeAsync(BETWEEN_REQUESTS_MS);
await vi.advanceTimersByTimeAsync(0);
await searchPromise;
expect(global.fetch).toHaveBeenCalledTimes(4); // 1 search + 3 book lookups
});
});
@@ -1,4 +1,4 @@
import { describe, expect, it, vi } from 'vitest';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import * as fetchWithThrottleModule from '../../fetch-with-throttle';
import { HardcoverClient } from './hardcover.client';
@@ -8,9 +8,14 @@ vi.mock('../../fetch-with-throttle', () => ({
}));
describe('HardcoverClient', () => {
const client = new HardcoverClient();
let client: HardcoverClient;
const apiKey = 'test-api-key';
beforeEach(() => {
client = new HardcoverClient();
vi.clearAllMocks();
});
it('passes the apiKey directly to the Authorization header', async () => {
const mockFetch = vi.mocked(fetchWithThrottleModule.fetchWithThrottle);
mockFetch.mockResolvedValue({
@@ -1,6 +1,11 @@
import { ConflictException, ForbiddenException, NotFoundException } from '@nestjs/common';
import { Test } from '@nestjs/testing';
vi.mock('bcryptjs', () => ({
hash: vi.fn((value: string) => Promise.resolve(`mock-hash:${value}`)),
compare: vi.fn((plain: string, hashed: string) => Promise.resolve(hashed === `mock-hash:${plain}`)),
}));
import { DB } from '../../../db';
import { OpdsUserService } from '../opds-user.service';
@@ -1,6 +1,7 @@
import { FileEventProcessorService } from './file-event-processor.service';
import type { Mocked, MockedFunction } from 'vitest';
import { ScannerRepository } from './scanner.repository';
import { Logger } from '@nestjs/common';
vi.mock('fs/promises', () => ({
stat: vi.fn(),
@@ -69,6 +70,10 @@ function makeFileStat(overrides: Partial<{ ino: number; size: number; mtime: Dat
beforeEach(() => {
vi.clearAllMocks();
vi.spyOn(Logger.prototype, 'log').mockImplementation(() => undefined);
vi.spyOn(Logger.prototype, 'warn').mockImplementation(() => undefined);
vi.spyOn(Logger.prototype, 'debug').mockImplementation(() => undefined);
vi.spyOn(Logger.prototype, 'error').mockImplementation(() => undefined);
mockRepo.markBooksAsMissing.mockResolvedValue(undefined);
mockRepo.markBooksAsPresent.mockResolvedValue(undefined);
mockRepo.createBookFile.mockResolvedValue({} as any);
+12 -1
View File
@@ -4,16 +4,27 @@ import { tmpdir } from 'os';
import { findBookCandidates, buildSingleBookCandidate } from './walk';
let suiteRoot: string;
let root: string;
let caseId = 0;
beforeAll(async () => {
suiteRoot = await mkdtemp(join(tmpdir(), 'scanner-walk-suite-'));
});
beforeEach(async () => {
root = await mkdtemp(join(tmpdir(), 'scanner-walk-'));
root = join(suiteRoot, `case-${caseId++}`);
await mkdir(root, { recursive: true });
});
afterEach(async () => {
await rm(root, { recursive: true, force: true });
});
afterAll(async () => {
await rm(suiteRoot, { recursive: true, force: true });
});
// Helpers
async function dir(...parts: string[]): Promise<string> {
const p = join(root, ...parts);
@@ -10,7 +10,7 @@ vi.mock('fs/promises', async (importOriginal) => {
};
});
import { ConflictException, NotFoundException } from '@nestjs/common';
import { ConflictException, Logger, NotFoundException } from '@nestjs/common';
import type { MockedFunction } from 'vitest';
import type { Dirent } from 'fs';
import { readdir, stat } from 'fs/promises';
@@ -152,6 +152,10 @@ function awaitScan(repo: ReturnType<typeof makeRepo>): Promise<void> {
beforeEach(() => {
vi.clearAllMocks();
vi.spyOn(Logger.prototype, 'log').mockImplementation(() => undefined);
vi.spyOn(Logger.prototype, 'warn').mockImplementation(() => undefined);
vi.spyOn(Logger.prototype, 'debug').mockImplementation(() => undefined);
vi.spyOn(Logger.prototype, 'error').mockImplementation(() => undefined);
mockFindCandidates.mockResolvedValue([]);
mockFindLooseCandidates.mockResolvedValue([]);
mockBuildSingleCandidate.mockResolvedValue(null);
@@ -9,7 +9,7 @@ vi.mock('../metadata/lib/mobi-parser', () => ({ parseMobiFile: vi.fn() }));
vi.mock('../metadata/lib/pdf-parser', () => ({ parsePdfFile: vi.fn() }));
import { access as fsAccess } from 'fs/promises';
import { BadRequestException, ConflictException } from '@nestjs/common';
import { BadRequestException, ConflictException, Logger } from '@nestjs/common';
import { extractEpubMetadata } from '../metadata/lib/epub';
import { UploadService } from './upload.service';
@@ -57,6 +57,10 @@ describe('UploadService', () => {
beforeEach(() => {
vi.resetAllMocks();
vi.spyOn(Logger.prototype, 'log').mockImplementation(() => undefined);
vi.spyOn(Logger.prototype, 'warn').mockImplementation(() => undefined);
vi.spyOn(Logger.prototype, 'debug').mockImplementation(() => undefined);
vi.spyOn(Logger.prototype, 'error').mockImplementation(() => undefined);
service = new UploadService(db as any, appSettings as any, libraryService as any, validator as any, storage as any, processor as any);
validator.sanitizeFilename.mockReturnValue('book.epub');