kt-template-admin/test/api/system/network.spec.ts

199 lines
5.7 KiB
TypeScript

import {
createNetworkDdnsRecord,
createNetworkPortForward,
deleteNetworkDdnsRecord,
deleteNetworkPortForward,
disableNetworkPortForwardKeeper,
enableNetworkPortForwardKeeper,
getNetworkAgentStatus,
getNetworkDdnsList,
getNetworkDdnsProviderStatus,
getNetworkDdnsSourceOptions,
getNetworkManagementEventsUrl,
getNetworkPortForwardEndpointHistory,
getNetworkPortForwardList,
probeNetworkPortForward,
retryNetworkDdnsRecord,
retryNetworkPortForward,
updateNetworkDdnsRecord,
updateNetworkPortForward,
} from '@test-source/apps/web-antdv-next/src/api/system/network';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { requestClient } from '#/api/request';
vi.mock('#/api/request', () => ({
requestClient: {
delete: vi.fn(),
get: vi.fn(),
getBaseUrl: vi.fn(() => '/api'),
post: vi.fn(),
put: vi.fn(),
},
}));
const input = {
externalPort: 45_678,
internalPort: 45_678,
name: 'Game UDP',
protocol: 'udp' as const,
remark: 'managed mapping',
};
const ddnsInput = {
domain: 'kwitsukasa.top',
enabled: true,
name: 'NAS IPv4',
portForwardId: '90071992547409930',
recordType: 'A' as const,
remark: 'managed DNS',
sourceType: 'port_forward_ipv4' as const,
subDomain: 'nas',
};
describe('system network api', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('uses the persisted CRUD endpoints without any router secret', async () => {
await getNetworkPortForwardList({ pageNo: 1, pageSize: 20 });
await createNetworkPortForward(input);
await updateNetworkPortForward('mapping-1', input);
await deleteNetworkPortForward('mapping-1');
expect(requestClient.get).toHaveBeenCalledWith(
'/system/network/port-forward/list',
{ params: { pageNo: 1, pageSize: 20 } },
);
expect(requestClient.post).toHaveBeenCalledWith(
'/system/network/port-forward',
input,
);
expect(requestClient.put).toHaveBeenCalledWith(
'/system/network/port-forward/mapping-1',
input,
);
expect(requestClient.delete).toHaveBeenCalledWith(
'/system/network/port-forward/mapping-1',
);
expect(JSON.stringify(input)).not.toContain('password');
});
it('uses distinct reconciliation and Keeper action endpoints', async () => {
await retryNetworkPortForward('mapping-1');
await enableNetworkPortForwardKeeper('mapping-1');
await disableNetworkPortForwardKeeper('mapping-1');
await probeNetworkPortForward('mapping-1');
expect(requestClient.post).toHaveBeenNthCalledWith(
1,
'/system/network/port-forward/mapping-1/retry',
);
expect(requestClient.post).toHaveBeenNthCalledWith(
2,
'/system/network/port-forward/mapping-1/keeper/enable',
);
expect(requestClient.post).toHaveBeenNthCalledWith(
3,
'/system/network/port-forward/mapping-1/keeper/disable',
);
expect(requestClient.post).toHaveBeenNthCalledWith(
4,
'/system/network/port-forward/mapping-1/probe',
);
});
it('loads Agent status and paged endpoint history independently', async () => {
await getNetworkAgentStatus();
await getNetworkPortForwardEndpointHistory('mapping-1', {
pageNo: 2,
pageSize: 10,
});
expect(requestClient.get).toHaveBeenNthCalledWith(
1,
'/system/network/agent/status',
);
expect(requestClient.get).toHaveBeenNthCalledWith(
2,
'/system/network/port-forward/mapping-1/endpoint-history',
{ params: { pageNo: 2, pageSize: 10 } },
);
});
it('uses the dual-stack DDNS CRUD endpoints without credential fields', async () => {
await getNetworkDdnsList({
pageNo: 1,
pageSize: 20,
recordType: 'AAAA',
});
await getNetworkDdnsSourceOptions('A');
await getNetworkDdnsProviderStatus();
await createNetworkDdnsRecord(ddnsInput);
await updateNetworkDdnsRecord('ddns-1', ddnsInput);
await retryNetworkDdnsRecord('ddns-1');
await deleteNetworkDdnsRecord('ddns-1');
expect(requestClient.get).toHaveBeenNthCalledWith(
1,
'/system/network/ddns/list',
{ params: { pageNo: 1, pageSize: 20, recordType: 'AAAA' } },
);
expect(requestClient.get).toHaveBeenNthCalledWith(
2,
'/system/network/ddns/source-options',
{ params: { recordType: 'A' } },
);
expect(requestClient.get).toHaveBeenNthCalledWith(
3,
'/system/network/ddns/provider-status',
);
expect(requestClient.post).toHaveBeenNthCalledWith(
1,
'/system/network/ddns',
ddnsInput,
);
expect(requestClient.put).toHaveBeenCalledWith(
'/system/network/ddns/ddns-1',
ddnsInput,
);
expect(requestClient.post).toHaveBeenNthCalledWith(
2,
'/system/network/ddns/ddns-1/retry',
);
expect(requestClient.delete).toHaveBeenCalledWith(
'/system/network/ddns/ddns-1',
);
expect(JSON.stringify(ddnsInput)).not.toMatch(
/secret|credential|password|token/i,
);
});
it('keeps AAAA source selection server-controlled and free of a mapping ID', async () => {
const ipv6Input = {
...ddnsInput,
portForwardId: undefined,
recordType: 'AAAA' as const,
sourceType: 'agent_ipv6' as const,
subDomain: 'nas6',
};
await createNetworkDdnsRecord(ipv6Input);
expect(requestClient.post).toHaveBeenCalledWith(
'/system/network/ddns',
ipv6Input,
);
});
it('builds the credentialed SSE URL with an optional replay cursor', () => {
expect(getNetworkManagementEventsUrl()).toBe(
'/api/system/network/events/stream',
);
expect(getNetworkManagementEventsUrl('network-event-1')).toBe(
'/api/system/network/events/stream?lastEventId=network-event-1',
);
});
});