🐛 fix: AAAA records with the right address, fix silent update-failure logging, accept configs regardless of key order

This commit is contained in:
2026-07-01 13:27:27 +07:00
parent e5c9aca45d
commit 389656f763
3 changed files with 30 additions and 30 deletions
+20 -11
View File
@@ -1,28 +1,27 @@
import { describe, expect, test } from "bun:test"; import { describe, expect, test } from "bun:test";
import { import {
arraysEqual, hasExactKeys,
isCloudflareConfig, isCloudflareConfig,
isEnviromentTokenPlaceholder, isEnviromentTokenPlaceholder,
isZoneConfig, isZoneConfig,
parseEnvironmentTokenPlaceholderName, parseEnvironmentTokenPlaceholderName,
} from "./config-validation"; } from "./config-validation";
describe("arraysEqual", () => { describe("hasExactKeys", () => {
test("returns true for identical arrays", () => { test("returns true when the object has exactly the given keys", () => {
expect(arraysEqual(["a", "b"], ["a", "b"])).toBe(true); expect(hasExactKeys({ a: 1, b: 2 }, ["a", "b"])).toBe(true);
}); });
test("returns false for different length arrays", () => { test("is order independent", () => {
expect(arraysEqual(["a"], ["a", "b"])).toBe(false); expect(hasExactKeys({ b: 2, a: 1 }, ["a", "b"])).toBe(true);
}); });
test("returns false for different order", () => { test("returns false when a key is missing", () => {
expect(arraysEqual(["a", "b"], ["b", "a"])).toBe(false); expect(hasExactKeys({ a: 1 }, ["a", "b"])).toBe(false);
}); });
test("returns false when either input is nullish", () => { test("returns false when there is an extra key", () => {
expect(arraysEqual(null, ["a"])).toBe(false); expect(hasExactKeys({ a: 1, b: 2, c: 3 }, ["a", "b"])).toBe(false);
expect(arraysEqual(["a"], undefined)).toBe(false);
}); });
}); });
@@ -74,6 +73,11 @@ describe("isZoneConfig", () => {
expect(isZoneConfig(null)).toBe(false); expect(isZoneConfig(null)).toBe(false);
expect(isZoneConfig("not-an-object")).toBe(false); expect(isZoneConfig("not-an-object")).toBe(false);
}); });
test("accepts a zone config whose keys are in a different order", () => {
const { proxied, ttl, ...rest } = validZone;
expect(isZoneConfig({ ...rest, proxied, ttl })).toBe(true);
});
}); });
describe("isCloudflareConfig", () => { describe("isCloudflareConfig", () => {
@@ -113,6 +117,11 @@ describe("isCloudflareConfig", () => {
expect(isCloudflareConfig("not-an-object")).toBe(false); expect(isCloudflareConfig("not-an-object")).toBe(false);
}); });
test("accepts a config whose top-level keys are in a different order", () => {
const { zone, ...rest } = validConfig;
expect(isCloudflareConfig({ zone, ...rest })).toBe(true);
});
test("rejects an env token placeholder whose variable is unset", () => { test("rejects an env token placeholder whose variable is unset", () => {
delete process.env.MISSING_TOKEN_VAR; delete process.env.MISSING_TOKEN_VAR;
const onMissing = () => {}; const onMissing = () => {};
+6 -14
View File
@@ -13,18 +13,11 @@ export interface CloudflareConfig {
zone: Array<ZoneConfig>; zone: Array<ZoneConfig>;
} }
export function arraysEqual( export function hasExactKeys(object: Record<string, unknown>, keys: string[]): boolean {
a: unknown[] | null | undefined, const objectKeys = Object.keys(object);
b: unknown[] | null | undefined, if (objectKeys.length !== keys.length) return false;
): boolean {
if (a === b) return true;
if (a == null || b == null) return false;
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; ++i) { return keys.every((key) => objectKeys.includes(key));
if (a[i] !== b[i]) return false;
}
return true;
} }
export function isEnviromentTokenPlaceholder(token: string): boolean { export function isEnviromentTokenPlaceholder(token: string): boolean {
@@ -41,8 +34,7 @@ export function isZoneConfig(input: unknown): input is ZoneConfig {
const object = input as Record<string, unknown>; const object = input as Record<string, unknown>;
if (!arraysEqual(Object.keys(object), ["id", "type", "name", "content", "ttl", "proxied"])) if (!hasExactKeys(object, ["id", "type", "name", "content", "ttl", "proxied"])) return false;
return false;
return ( return (
!!object.id && !!object.id &&
@@ -67,7 +59,7 @@ export function isCloudflareConfig(
const object = input as Record<string, unknown>; const object = input as Record<string, unknown>;
if (!arraysEqual(Object.keys(object), ["token", "updateInterval", "zone"])) return false; if (!hasExactKeys(object, ["token", "updateInterval", "zone"])) return false;
const res = const res =
!!object.token && !!object.token &&
+4 -5
View File
@@ -97,13 +97,13 @@ class Updater {
.createRecord({ .createRecord({
type: zone.type, type: zone.type,
name: zone.name, name: zone.name,
content: IPv4, content: newContent,
ttl: zone.ttl, ttl: zone.ttl,
proxied: zone.proxied, proxied: zone.proxied,
}) })
.catch((err) => { .catch((err) => {
Logger.error(`Unable to create record: ${err.message}`); Logger.error(`Unable to create record: ${err.message}`);
return err; return undefined;
}); });
if (result) Logger.info(`Created [${zone.type}] (${zone.name} -> ${newContent})`); if (result) Logger.info(`Created [${zone.type}] (${zone.name} -> ${newContent})`);
@@ -129,7 +129,7 @@ class Updater {
}) })
.catch((err) => { .catch((err) => {
Logger.error(`Unable to update record: ${err.message}`); Logger.error(`Unable to update record: ${err.message}`);
return err; return undefined;
}); });
if (updateResult) if (updateResult)
@@ -148,8 +148,7 @@ class Updater {
}) })
.catch((err) => { .catch((err) => {
Logger.error(`Unable to update record: ${err.message}`); Logger.error(`Unable to update record: ${err.message}`);
console.log(err); return undefined;
return;
}); });
if (updateResult) if (updateResult)