Files
TREK/server/tests/unit/services/airtrailTestConnection.test.ts
T
Maurice 8350d04a63 refactor(db): the Immich, Synology and AirTrail columns of users get repositories of their own
UserImmichRepository, UserSynologyRepository and UserAirtrailRepository hold the eighteen connection statements UsersRepository carried, on the same users table and with their statements unchanged, provided through a small module as factories over the EntityManager. ImmichService, SynologyService, AirtrailService and JourneyService inject them instead of the account repository. UserRow drops the dead immich_access_token column (the table keeps it), and a shape test pins the account repository's size.
2026-10-10 20:15:57 +02:00

92 lines
3.9 KiB
TypeScript

import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest';
// Avoid any real DNS/network from the SSRF guard during saveSettings and the probe.
vi.mock('../../../src/utils/ssrfGuard', () => ({
checkSsrf: vi.fn(async () => ({ allowed: true, isPrivate: false })),
safeFetch: vi.fn(),
}));
import { db } from '../../../src/db/database';
import { createUser } from '../../helpers/factories';
import { AirtrailService } from '../../../src/nest/integrations/airtrail.service';
import type { AirtrailClient } from '../../../src/nest/integrations/airtrail.client';
import { AuditService } from '../../../src/nest/audit/audit.service';
import { createTestOrm, type TestOrm } from '../../helpers/test-orm';
import { AuditLog } from '../../../src/db/entities/AuditLog.entity';
import { Users } from '../../../src/db/entities/Users.entity';
import { UserAirtrailRepository } from '../../../src/db/repositories/UserAirtrail.repository';
// The probe is the only call that would leave the process, so the client is a
// stub; the credential handling around it runs against the real row.
const listFlights = vi.fn();
let svc: AirtrailService;
let t: TestOrm;
beforeAll(async () => {
t = await createTestOrm(db);
svc = new AirtrailService(
new UserAirtrailRepository(t.orm.em),
new AuditService(t.repo(AuditLog), t.repo(Users)),
{ listFlights } as unknown as AirtrailClient,
);
});
afterAll(async () => {
await t.close();
});
const MASK = '••••••••';
beforeEach(() => {
listFlights.mockReset();
listFlights.mockResolvedValue([]);
});
describe('airtrail test connection keeps the stored key with its host', () => {
it('refuses a blank key field against a different host than the stored one before anything is sent', async () => {
// The form prefills the address and never the key, so retyping the URL and
// pressing Test is the ordinary way to move an instance. Falling back to the
// stored key here would carry it to whatever host was just typed.
const { user } = createUser(db);
await svc.saveSettings(user.id, 'https://old.example', 'stored-key', false, false, null);
const out = await svc.testConnection(user.id, 'https://someone-elses.example', undefined, false);
expect(out.connected).toBe(false);
expect(out.error).toContain('https://old.example');
expect(listFlights).not.toHaveBeenCalled();
});
it('treats the mask against a different port as a blank field, not a key', async () => {
const { user } = createUser(db);
await svc.saveSettings(user.id, 'https://old.example', 'stored-key', false, false, null);
const out = await svc.testConnection(user.id, 'https://old.example:8443', MASK, false);
expect(out.connected).toBe(false);
expect(listFlights).not.toHaveBeenCalled();
});
it('still tests a path edit on the same origin with the stored key', async () => {
// The same instance under a corrected path is where the key was issued, and
// refusing it would make every trailing-slash edit demand the key again.
const { user } = createUser(db);
await svc.saveSettings(user.id, 'https://at.example/api', 'stored-key', false, false, null);
const out = await svc.testConnection(user.id, 'https://at.example/', undefined, false);
expect(out.connected).toBe(true);
expect(listFlights).toHaveBeenCalledWith({ baseUrl: 'https://at.example/', apiKey: 'stored-key', allowInsecureTls: false });
});
it('uses a key typed for the new host as typed, so moving an instance and testing it first still works', async () => {
const { user } = createUser(db);
await svc.saveSettings(user.id, 'https://old.example', 'stored-key', false, false, null);
const out = await svc.testConnection(user.id, 'https://new.example', 'minted-for-new', false);
expect(out.connected).toBe(true);
expect(listFlights).toHaveBeenCalledWith({ baseUrl: 'https://new.example', apiKey: 'minted-for-new', allowInsecureTls: false });
});
});