quality / changes (push) Successful in 9s
quality / web (push) Skipped
quality / commitlint (push) Skipped
quality / docker-check (push) Skipped
CD / update-wiki (push) Successful in 4s
quality / api (push) Successful in 48s
CD / quality (push) Successful in 1m2s
CD / publish (push) Successful in 1m26s
Больше не авто-подхватывать чужой CNAME в A-режиме (это стирало IP). Перед публикацией A удаляем конфликтующий CNAME и устаревшие A на том же имени. Co-authored-by: Cursor <[email protected]>
225 lines
6.3 KiB
TypeScript
225 lines
6.3 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { createMemoryDb, repos, runMigrations } from "@cfdm/db";
|
|
import { SYNC_SYNCED } from "@cfdm/shared";
|
|
import type { CloudflareClient } from "../src/lib/cf-client.js";
|
|
import { updateConfig } from "../src/services/service-config-service.js";
|
|
|
|
type CfRec = {
|
|
id: string;
|
|
type: string;
|
|
name: string;
|
|
content: string;
|
|
ttl: number;
|
|
proxied: boolean;
|
|
};
|
|
|
|
function setupDb() {
|
|
const { db, sqlite } = createMemoryDb();
|
|
runMigrations(sqlite);
|
|
return db;
|
|
}
|
|
|
|
describe("DNS reconcile for extra FQDN", () => {
|
|
it("publishes extra A even when local/CF already have CNAME on same name", async () => {
|
|
const db = setupDb();
|
|
const remote: CfRec[] = [
|
|
{
|
|
id: "cf-cname-nsgt",
|
|
type: "CNAME",
|
|
name: "nsgt.example.com",
|
|
content: "legacy.example.com",
|
|
ttl: 1,
|
|
proxied: false,
|
|
},
|
|
];
|
|
const created: Array<{ type: string; name: string; content: string }> = [];
|
|
const deleted: string[] = [];
|
|
|
|
const cf = {
|
|
listDnsRecords: async () => remote,
|
|
createDnsRecord: async (
|
|
_zoneId: string,
|
|
payload: { type: string; name: string; content: string },
|
|
) => {
|
|
created.push(payload);
|
|
const rec: CfRec = {
|
|
id: `cf-new-${created.length}`,
|
|
type: payload.type,
|
|
name: payload.name,
|
|
content: payload.content,
|
|
ttl: 1,
|
|
proxied: false,
|
|
};
|
|
remote.push(rec);
|
|
return rec;
|
|
},
|
|
updateDnsRecord: async (
|
|
_zoneId: string,
|
|
id: string,
|
|
payload: { type: string; name: string; content: string },
|
|
) => {
|
|
const idx = remote.findIndex((r) => r.id === id);
|
|
const rec: CfRec = {
|
|
id,
|
|
type: payload.type,
|
|
name: payload.name,
|
|
content: payload.content,
|
|
ttl: 1,
|
|
proxied: false,
|
|
};
|
|
if (idx >= 0) remote[idx] = rec;
|
|
else remote.push(rec);
|
|
return rec;
|
|
},
|
|
deleteDnsRecord: async (_zoneId: string, id: string) => {
|
|
deleted.push(id);
|
|
const idx = remote.findIndex((r) => r.id === id);
|
|
if (idx >= 0) remote.splice(idx, 1);
|
|
},
|
|
verifyToken: async () => true,
|
|
listZones: async () => [
|
|
{ id: "zone-1", name: "example.com", status: "active" },
|
|
],
|
|
} as unknown as CloudflareClient;
|
|
|
|
const domain = repos.createDomain(db, null, "example.com", "zone-1");
|
|
// Leftover local CNAME (previous service / manual import).
|
|
repos.insertDnsRecord(
|
|
db,
|
|
domain.id,
|
|
"CNAME",
|
|
"nsgt.example.com",
|
|
"legacy.example.com",
|
|
1,
|
|
false,
|
|
null,
|
|
SYNC_SYNCED,
|
|
"cloudflare",
|
|
"cf-cname-nsgt",
|
|
);
|
|
|
|
const service = repos.createService(db, "MSK Hip", "tg-msk-hip");
|
|
repos.setServiceEnabled(db, service.id, true);
|
|
|
|
const view = await updateConfig(db, cf, service.id, {
|
|
ips: ["130.49.213.176"],
|
|
domains: [
|
|
{
|
|
fqdn: "gt.example.com",
|
|
target_ips: ["130.49.213.176"],
|
|
},
|
|
{
|
|
fqdn: "nsgt.example.com",
|
|
target_ips: ["130.49.213.176"],
|
|
},
|
|
],
|
|
});
|
|
|
|
const nsgt = view.domains.find((d) => d.fqdn === "nsgt.example.com");
|
|
expect(nsgt?.record_type).toBe("A");
|
|
expect(nsgt?.target_ips).toEqual(["130.49.213.176"]);
|
|
expect(nsgt?.target_cname).toBeFalsy();
|
|
|
|
const binding = repos
|
|
.listBindingsByService(db, service.id)
|
|
.find((b) => b.hostname === "nsgt")!;
|
|
expect(repos.listBindingIps(db, binding.id)).toEqual(["130.49.213.176"]);
|
|
expect(deleted).toContain("cf-cname-nsgt");
|
|
expect(
|
|
created.some(
|
|
(r) =>
|
|
r.type === "A" &&
|
|
(r.name === "nsgt" || r.name === "nsgt.example.com") &&
|
|
r.content === "130.49.213.176",
|
|
),
|
|
).toBe(true);
|
|
expect(remote.some((r) => r.type === "CNAME" && r.name.includes("nsgt"))).toBe(
|
|
false,
|
|
);
|
|
expect(
|
|
remote.some(
|
|
(r) =>
|
|
r.type === "A" &&
|
|
r.name.includes("nsgt") &&
|
|
r.content === "130.49.213.176",
|
|
),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("replaces stale A content for extra FQDN on the same hostname", async () => {
|
|
const db = setupDb();
|
|
const remote: CfRec[] = [
|
|
{
|
|
id: "cf-stale-a",
|
|
type: "A",
|
|
name: "nsgt.example.com",
|
|
content: "1.1.1.1",
|
|
ttl: 1,
|
|
proxied: false,
|
|
},
|
|
];
|
|
const deleted: string[] = [];
|
|
const created: Array<{ type: string; name: string; content: string }> = [];
|
|
|
|
const cf = {
|
|
listDnsRecords: async () => [...remote],
|
|
createDnsRecord: async (
|
|
_zoneId: string,
|
|
payload: { type: string; name: string; content: string },
|
|
) => {
|
|
created.push(payload);
|
|
const rec: CfRec = {
|
|
id: `cf-new-${created.length}`,
|
|
type: payload.type,
|
|
name: payload.name,
|
|
content: payload.content,
|
|
ttl: 1,
|
|
proxied: false,
|
|
};
|
|
remote.push(rec);
|
|
return rec;
|
|
},
|
|
updateDnsRecord: async (
|
|
_zoneId: string,
|
|
id: string,
|
|
payload: { type: string; name: string; content: string },
|
|
) => ({
|
|
id,
|
|
type: payload.type,
|
|
name: payload.name,
|
|
content: payload.content,
|
|
ttl: 1,
|
|
proxied: false,
|
|
}),
|
|
deleteDnsRecord: async (_zoneId: string, id: string) => {
|
|
deleted.push(id);
|
|
const idx = remote.findIndex((r) => r.id === id);
|
|
if (idx >= 0) remote.splice(idx, 1);
|
|
},
|
|
verifyToken: async () => true,
|
|
listZones: async () => [
|
|
{ id: "zone-1", name: "example.com", status: "active" },
|
|
],
|
|
} as unknown as CloudflareClient;
|
|
|
|
repos.createDomain(db, null, "example.com", "zone-1");
|
|
const service = repos.createService(db, "MSK", "msk");
|
|
repos.setServiceEnabled(db, service.id, true);
|
|
|
|
await updateConfig(db, cf, service.id, {
|
|
ips: ["130.49.213.176"],
|
|
domains: [
|
|
{ fqdn: "nsgt.example.com", target_ips: ["130.49.213.176"] },
|
|
],
|
|
});
|
|
|
|
expect(deleted).toContain("cf-stale-a");
|
|
expect(
|
|
created.some(
|
|
(r) => r.type === "A" && r.content === "130.49.213.176",
|
|
),
|
|
).toBe(true);
|
|
expect(remote.map((r) => r.content)).toEqual(["130.49.213.176"]);
|
|
});
|
|
});
|