码桶

发现社区成员的开源项目

main
monitors.test.js9.8 KB
import { onRequestGet, onRequestPost, onRequestDelete } from '../../functions/api/monitors.js';
import { createMockKV, createMockContext, getResponseJson } from './_helpers.js';

/**
 * Helper: build a context for monitor endpoints with a given user.
 */
function createMonitorContext(overrides = {}) {
    const store = overrides.store || {};
    const kv = createMockKV(store);
    const request = overrides.request || new Request('http://localhost/api/monitors', { method: 'GET' });

    return createMockContext({
        kv,
        request,
        data: {
            user: { username: overrides.username || 'testuser', role: 'user' },
            ...overrides.data,
        },
        ...overrides,
    });
}

/**
 * Helper: build a POST request with JSON body for monitors.
 */
function makeMonitorPostRequest(body) {
    return new Request('http://localhost/api/monitors', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
    });
}

/**
 * Helper: build a DELETE request with an ID query param.
 */
function makeMonitorDeleteRequest(id) {
    const url = id ? `http://localhost/api/monitors?id=${id}` : 'http://localhost/api/monitors';
    return new Request(url, {
        method: 'DELETE',
        headers: { 'Content-Type': 'application/json' },
    });
}

// ---------------------------------------------------------------------------

describe('GET /api/monitors', () => {
    it('returns empty array when no monitors exist', async () => {
        const ctx = createMonitorContext();

        const res = await onRequestGet(ctx);
        const json = await getResponseJson(res);

        expect(res.status).toBe(200);
        expect(json.success).toBe(true);
        expect(json.monitors).toEqual([]);
    });

    it('returns existing monitors for the user', async () => {
        const monitors = [
            { id: 'mon-1', zoneId: 'z1', recordType: 'A', recordName: 'test.example.com', expectedContent: '1.2.3.4' },
        ];
        const ctx = createMonitorContext({
            store: { 'DNS_MONITORS:testuser': JSON.stringify(monitors) },
        });

        const res = await onRequestGet(ctx);
        const json = await getResponseJson(res);

        expect(res.status).toBe(200);
        expect(json.monitors).toHaveLength(1);
        expect(json.monitors[0].id).toBe('mon-1');
    });

    it('returns 500 when KV is not configured', async () => {
        const ctx = createMockContext({
            request: new Request('http://localhost/api/monitors', { method: 'GET' }),
            env: { CF_DNS_KV: undefined },
            data: { user: { username: 'testuser' } },
        });

        const res = await onRequestGet(ctx);
        expect(res.status).toBe(500);
    });
});

describe('POST /api/monitors', () => {
    const validMonitorBody = {
        zoneId: 'zone-abc',
        zoneName: 'example.com',
        recordType: 'A',
        recordName: 'www.example.com',
        expectedContent: '93.184.216.34',
    };

    it('creates a monitor with valid data', async () => {
        const ctx = createMonitorContext({
            request: makeMonitorPostRequest(validMonitorBody),
        });

        const res = await onRequestPost(ctx);
        const json = await getResponseJson(res);

        expect(res.status).toBe(201);
        expect(json.success).toBe(true);
        expect(json.monitor).toBeDefined();
        expect(json.monitor.zoneId).toBe('zone-abc');
        expect(json.monitor.recordType).toBe('A');
        expect(json.monitor.recordName).toBe('www.example.com');
        expect(json.monitor.expectedContent).toBe('93.184.216.34');
        expect(json.monitor.enabled).toBe(true);
        expect(json.monitor.lastStatus).toBe('unknown');
        expect(json.monitor.id).toBeDefined();
        expect(json.monitor.createdAt).toBeDefined();
    });

    it('uppercases the recordType', async () => {
        const ctx = createMonitorContext({
            request: makeMonitorPostRequest({ ...validMonitorBody, recordType: 'cname' }),
        });

        const res = await onRequestPost(ctx);
        const json = await getResponseJson(res);

        expect(res.status).toBe(201);
        expect(json.monitor.recordType).toBe('CNAME');
    });

    it('returns 400 when zoneId is missing', async () => {
        const { zoneId, ...bodyWithout } = validMonitorBody;
        const ctx = createMonitorContext({
            request: makeMonitorPostRequest(bodyWithout),
        });

        const res = await onRequestPost(ctx);
        const json = await getResponseJson(res);

        expect(res.status).toBe(400);
        expect(json.error).toMatch(/missing required field/i);
    });

    it('returns 400 when recordType is missing', async () => {
        const { recordType, ...bodyWithout } = validMonitorBody;
        const ctx = createMonitorContext({
            request: makeMonitorPostRequest(bodyWithout),
        });

        const res = await onRequestPost(ctx);
        expect(res.status).toBe(400);
    });

    it('returns 400 when recordName is missing', async () => {
        const { recordName, ...bodyWithout } = validMonitorBody;
        const ctx = createMonitorContext({
            request: makeMonitorPostRequest(bodyWithout),
        });

        const res = await onRequestPost(ctx);
        expect(res.status).toBe(400);
    });

    it('returns 400 when expectedContent is missing', async () => {
        const { expectedContent, ...bodyWithout } = validMonitorBody;
        const ctx = createMonitorContext({
            request: makeMonitorPostRequest(bodyWithout),
        });

        const res = await onRequestPost(ctx);
        expect(res.status).toBe(400);
    });

    it('enforces 50 monitor limit', async () => {
        // Pre-populate 50 monitors
        const existingMonitors = Array.from({ length: 50 }, (_, i) => ({
            id: `mon-${i}`,
            zoneId: 'z1',
            recordType: 'A',
            recordName: `r${i}.example.com`,
            expectedContent: '1.2.3.4',
        }));

        const ctx = createMonitorContext({
            store: { 'DNS_MONITORS:testuser': JSON.stringify(existingMonitors) },
            request: makeMonitorPostRequest(validMonitorBody),
        });

        const res = await onRequestPost(ctx);
        const json = await getResponseJson(res);

        expect(res.status).toBe(400);
        expect(json.error).toMatch(/maximum 50 monitors/i);
    });

    it('allows creation when under the limit (49 existing)', async () => {
        const existingMonitors = Array.from({ length: 49 }, (_, i) => ({
            id: `mon-${i}`,
            zoneId: 'z1',
            recordType: 'A',
            recordName: `r${i}.example.com`,
            expectedContent: '1.2.3.4',
        }));

        const ctx = createMonitorContext({
            store: { 'DNS_MONITORS:testuser': JSON.stringify(existingMonitors) },
            request: makeMonitorPostRequest(validMonitorBody),
        });

        const res = await onRequestPost(ctx);
        expect(res.status).toBe(201);
    });

    it('returns 500 when KV is not configured', async () => {
        const ctx = createMockContext({
            request: makeMonitorPostRequest(validMonitorBody),
            env: { CF_DNS_KV: undefined },
            data: { user: { username: 'testuser' } },
        });

        const res = await onRequestPost(ctx);
        expect(res.status).toBe(500);
    });
});

describe('DELETE /api/monitors', () => {
    it('deletes a monitor by ID', async () => {
        const monitors = [
            { id: 'mon-1', zoneId: 'z1', recordType: 'A', recordName: 'a.example.com', expectedContent: '1.2.3.4' },
            { id: 'mon-2', zoneId: 'z2', recordType: 'A', recordName: 'b.example.com', expectedContent: '5.6.7.8' },
        ];
        const store = { 'DNS_MONITORS:testuser': JSON.stringify(monitors) };
        const kv = createMockKV(store);

        const ctx = createMockContext({
            kv,
            request: makeMonitorDeleteRequest('mon-1'),
            data: { user: { username: 'testuser' } },
        });

        const res = await onRequestDelete(ctx);
        const json = await getResponseJson(res);

        expect(res.status).toBe(200);
        expect(json.success).toBe(true);

        // Verify KV was updated: only mon-2 should remain
        const putCall = kv.put.mock.calls.find(([key]) => key === 'DNS_MONITORS:testuser');
        expect(putCall).toBeDefined();
        const remaining = JSON.parse(putCall[1]);
        expect(remaining).toHaveLength(1);
        expect(remaining[0].id).toBe('mon-2');
    });

    it('returns 404 when monitor ID does not exist', async () => {
        const monitors = [
            { id: 'mon-1', zoneId: 'z1', recordType: 'A', recordName: 'a.example.com', expectedContent: '1.2.3.4' },
        ];
        const ctx = createMonitorContext({
            store: { 'DNS_MONITORS:testuser': JSON.stringify(monitors) },
            request: makeMonitorDeleteRequest('non-existent-id'),
        });

        const res = await onRequestDelete(ctx);
        const json = await getResponseJson(res);

        expect(res.status).toBe(404);
        expect(json.error).toMatch(/not found/i);
    });

    it('returns 400 when id parameter is missing', async () => {
        const ctx = createMonitorContext({
            request: makeMonitorDeleteRequest(null),
        });

        const res = await onRequestDelete(ctx);
        const json = await getResponseJson(res);

        expect(res.status).toBe(400);
        expect(json.error).toMatch(/missing id/i);
    });

    it('returns 500 when KV is not configured', async () => {
        const ctx = createMockContext({
            request: makeMonitorDeleteRequest('mon-1'),
            env: { CF_DNS_KV: undefined },
            data: { user: { username: 'testuser' } },
        });

        const res = await onRequestDelete(ctx);
        expect(res.status).toBe(500);
    });
});