Files
TREK/server/tests/unit/nest/integrations/airtrailTestConnection.test.ts
T
Maurice ae26a6c61c refactor(airtrail): save the settings in one transaction
The address and the key it may no longer carry now change together, as
they do for Dawarich, so the transaction ratchet drops the AirTrail entry.
The tests that pinned the old no-transaction shape now pin the new one.
2026-11-12 17:31:56 +01:00

104 lines
4.1 KiB
TypeScript

import { db } from '../../../../src/db/database';
import { AuditLog } from '../../../../src/db/entities/AuditLog.entity';
import { Users } from '../../../../src/db/entities/Users.entity';
import { UserAirtrailRepository } from '../../../../src/db/repositories/UserAirtrail.repository';
import { AuditService } from '../../../../src/nest/audit/audit.service';
import { UnitOfWork } from '../../../../src/nest/database/unit-of-work';
import type { AirtrailClient } from '../../../../src/nest/integrations/airtrail.client';
import { AirtrailService } from '../../../../src/nest/integrations/airtrail.service';
import { createUser } from '../../../helpers/factories';
import { createTestOrm, type TestOrm } from '../../../helpers/test-orm';
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(),
}));
// 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,
new UnitOfWork(t.orm.em),
);
});
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,
});
});
});