mirror of
https://github.com/louislam/uptime-kuma.git
synced 2026-01-31 11:03:11 +08:00
fix: noisy domain expiry checks in monitor editor and missing debuggability (#6637)
Co-authored-by: Frank Elsinga <frank@elsinga.de> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
process.env.UPTIME_KUMA_HIDE_LOG = [ "info_db", "info_server" ].join(",");
|
||||
|
||||
const { describe, test, mock } = require("node:test");
|
||||
const { describe, test, mock, before, after } = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
const DomainExpiry = require("../../server/model/domain_expiry");
|
||||
const mockWebhook = require("./notification-providers/mock-webhook");
|
||||
@@ -19,22 +19,217 @@ describe("Domain Expiry", () => {
|
||||
domainExpiryNotification: true
|
||||
};
|
||||
|
||||
test("getExpiryDate() returns correct expiry date for .wiki domain with no A record", async () => {
|
||||
before(async () => {
|
||||
await testDb.create();
|
||||
Notification.init();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
Settings.stopCacheCleaner();
|
||||
await testDb.destroy();
|
||||
});
|
||||
|
||||
test("getExpiryDate() returns correct expiry date for .wiki domain with no A record", async () => {
|
||||
const d = DomainExpiry.createByName("google.wiki");
|
||||
assert.deepEqual(await d.getExpiryDate(), new Date("2026-11-26T23:59:59.000Z"));
|
||||
});
|
||||
|
||||
test("forMonitor() retrieves expiration date for .com domain from RDAP", async () => {
|
||||
const domain = await DomainExpiry.forMonitor(monHttpCom);
|
||||
describe("checkSupport()", () => {
|
||||
test("allows and correctly parses http monitor with valid domain", async () => {
|
||||
const supportInfo = await DomainExpiry.checkSupport(monHttpCom);
|
||||
let expected = {
|
||||
domain: "google.com",
|
||||
tld: "com"
|
||||
};
|
||||
assert.deepStrictEqual(supportInfo, expected);
|
||||
});
|
||||
|
||||
describe("Target Validation", () => {
|
||||
test("throws error for empty string target", async () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
url: "",
|
||||
domainExpiryNotification: true
|
||||
};
|
||||
await assert.rejects(
|
||||
async () => await DomainExpiry.checkSupport(monitor),
|
||||
(error) => {
|
||||
assert.strictEqual(error.constructor.name, "TranslatableError");
|
||||
assert.strictEqual(error.message, "domain_expiry_unsupported_missing_target");
|
||||
return true;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("throws error for undefined target", async () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
domainExpiryNotification: true
|
||||
};
|
||||
await assert.rejects(
|
||||
async () => await DomainExpiry.checkSupport(monitor),
|
||||
(error) => {
|
||||
assert.strictEqual(error.constructor.name, "TranslatableError");
|
||||
assert.strictEqual(error.message, "domain_expiry_unsupported_missing_target");
|
||||
return true;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("throws error for null target", async () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
url: null,
|
||||
domainExpiryNotification: true
|
||||
};
|
||||
await assert.rejects(
|
||||
async () => await DomainExpiry.checkSupport(monitor),
|
||||
(error) => {
|
||||
assert.strictEqual(error.constructor.name, "TranslatableError");
|
||||
assert.strictEqual(error.message, "domain_expiry_unsupported_missing_target");
|
||||
return true;
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Domain Parsing", () => {
|
||||
test("throws error for invalid domain (no domain part)", async () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
url: "https://",
|
||||
domainExpiryNotification: true
|
||||
};
|
||||
await assert.rejects(
|
||||
async () => await DomainExpiry.checkSupport(monitor),
|
||||
(error) => {
|
||||
assert.strictEqual(error.constructor.name, "TranslatableError");
|
||||
assert.strictEqual(error.message, "domain_expiry_unsupported_invalid_domain");
|
||||
return true;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("throws error for IPv4 address instead of domain", async () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
url: "https://192.168.1.1",
|
||||
domainExpiryNotification: true
|
||||
};
|
||||
await assert.rejects(
|
||||
async () => await DomainExpiry.checkSupport(monitor),
|
||||
(error) => {
|
||||
assert.strictEqual(error.constructor.name, "TranslatableError");
|
||||
assert.strictEqual(error.message, "domain_expiry_unsupported_invalid_domain");
|
||||
return true;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("throws error for IPv6 address", async () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
url: "https://[2001:db8::1]",
|
||||
domainExpiryNotification: true
|
||||
};
|
||||
await assert.rejects(
|
||||
async () => await DomainExpiry.checkSupport(monitor),
|
||||
(error) => {
|
||||
assert.strictEqual(error.constructor.name, "TranslatableError");
|
||||
assert.strictEqual(error.message, "domain_expiry_unsupported_invalid_domain");
|
||||
return true;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("throws error for single-letter TLD", async () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
url: "https://example.x",
|
||||
domainExpiryNotification: true
|
||||
};
|
||||
await assert.rejects(
|
||||
async () => await DomainExpiry.checkSupport(monitor),
|
||||
(error) => {
|
||||
assert.strictEqual(error.constructor.name, "TranslatableError");
|
||||
assert.strictEqual(error.message, "domain_expiry_public_suffix_too_short");
|
||||
return true;
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Edge Cases & RDAP Support", () => {
|
||||
test("handles subdomain correctly", async () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
url: "https://api.staging.example.com/v1/users",
|
||||
domainExpiryNotification: true
|
||||
};
|
||||
const supportInfo = await DomainExpiry.checkSupport(monitor);
|
||||
assert.strictEqual(supportInfo.domain, "example.com");
|
||||
assert.strictEqual(supportInfo.tld, "com");
|
||||
});
|
||||
|
||||
test("handles complex subdomain correctly", async () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
url: "https://mail.subdomain.example.org",
|
||||
domainExpiryNotification: true
|
||||
};
|
||||
const supportInfo = await DomainExpiry.checkSupport(monitor);
|
||||
assert.strictEqual(supportInfo.domain, "example.org");
|
||||
assert.strictEqual(supportInfo.tld, "org");
|
||||
});
|
||||
|
||||
test("handles URL with port correctly", async () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
url: "https://example.com:8080/api",
|
||||
domainExpiryNotification: true
|
||||
};
|
||||
const supportInfo = await DomainExpiry.checkSupport(monitor);
|
||||
assert.strictEqual(supportInfo.domain, "example.com");
|
||||
assert.strictEqual(supportInfo.tld, "com");
|
||||
});
|
||||
|
||||
test("handles URL with query parameters correctly", async () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
url: "https://example.com/search?q=test&page=1",
|
||||
domainExpiryNotification: true
|
||||
};
|
||||
const supportInfo = await DomainExpiry.checkSupport(monitor);
|
||||
assert.strictEqual(supportInfo.domain, "example.com");
|
||||
assert.strictEqual(supportInfo.tld, "com");
|
||||
});
|
||||
|
||||
test("throws error for unsupported TLD without RDAP endpoint", async () => {
|
||||
const monitor = {
|
||||
type: "http",
|
||||
url: "https://example.localhost",
|
||||
domainExpiryNotification: true
|
||||
};
|
||||
await assert.rejects(
|
||||
async () => await DomainExpiry.checkSupport(monitor),
|
||||
(error) => {
|
||||
assert.strictEqual(error.constructor.name, "TranslatableError");
|
||||
assert.strictEqual(error.message, "domain_expiry_unsupported_unsupported_tld_no_rdap_endpoint");
|
||||
return true;
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("findByDomainNameOrCreate() retrieves expiration date for .com domain from RDAP", async () => {
|
||||
const domain = await DomainExpiry.findByDomainNameOrCreate("google.com");
|
||||
const expiryFromRdap = await domain.getExpiryDate(); // from RDAP
|
||||
assert.deepEqual(expiryFromRdap, new Date("2028-09-14T04:00:00.000Z"));
|
||||
});
|
||||
|
||||
test("checkExpiry() caches expiration date in database", async () => {
|
||||
await DomainExpiry.checkExpiry(monHttpCom); // RDAP -> Cache
|
||||
await DomainExpiry.checkExpiry("google.com"); // RDAP -> Cache
|
||||
const domain = await DomainExpiry.findByName("google.com");
|
||||
assert(Date.now() - domain.lastCheck < 5 * 1000);
|
||||
});
|
||||
@@ -60,27 +255,22 @@ describe("Domain Expiry", () => {
|
||||
const manyDays = 3650;
|
||||
setSetting("domainExpiryNotifyDays", [ manyDays ], "general");
|
||||
const [ , data ] = await Promise.all([
|
||||
DomainExpiry.sendNotifications(monHttpCom, [ notif ]),
|
||||
DomainExpiry.sendNotifications("google.com", [ notif ]),
|
||||
mockWebhook(hook.port, hook.url)
|
||||
]);
|
||||
assert.match(data.msg, /will expire in/);
|
||||
|
||||
setTimeout(async () => {
|
||||
Settings.stopCacheCleaner();
|
||||
await testDb.destroy();
|
||||
}, 200);
|
||||
});
|
||||
|
||||
test("sendNotifications() handles domain with null expiry without sending NaN", async () => {
|
||||
// Regression test for bug: "Domain name will expire in NaN days"
|
||||
// Mock forMonitor to return a bean with null expiry
|
||||
// Mock findByDomainNameOrCreate to return a bean with null expiry
|
||||
const mockDomain = {
|
||||
domain: "test-null.com",
|
||||
expiry: null,
|
||||
lastExpiryNotificationSent: null
|
||||
};
|
||||
|
||||
mock.method(DomainExpiry, "forMonitor", async () => mockDomain);
|
||||
mock.method(DomainExpiry, "findByDomainNameOrCreate", async () => mockDomain);
|
||||
|
||||
try {
|
||||
const hook = {
|
||||
@@ -88,12 +278,6 @@ describe("Domain Expiry", () => {
|
||||
"url": "should-not-be-called-null"
|
||||
};
|
||||
|
||||
const monTest = {
|
||||
type: "http",
|
||||
url: "https://test-null.com",
|
||||
domainExpiryNotification: true
|
||||
};
|
||||
|
||||
const notif = {
|
||||
name: "TestNullExpiry",
|
||||
config: JSON.stringify({
|
||||
@@ -107,7 +291,7 @@ describe("Domain Expiry", () => {
|
||||
// Race between sendNotifications and mockWebhook timeout
|
||||
// If webhook is called, we fail. If it times out, we pass.
|
||||
const result = await Promise.race([
|
||||
DomainExpiry.sendNotifications(monTest, [ notif ]),
|
||||
DomainExpiry.sendNotifications("test-null.com", [ notif ]),
|
||||
mockWebhook(hook.port, hook.url, 500).then(() => {
|
||||
throw new Error("Webhook was called but should not have been for null expiry");
|
||||
}).catch((e) => {
|
||||
@@ -126,26 +310,20 @@ describe("Domain Expiry", () => {
|
||||
|
||||
test("sendNotifications() handles domain with undefined expiry without sending NaN", async () => {
|
||||
try {
|
||||
// Mock forMonitor to return a bean with undefined expiry (newly created bean scenario)
|
||||
// Mock findByDomainNameOrCreate to return a bean with undefined expiry (newly created bean scenario)
|
||||
const mockDomain = {
|
||||
domain: "test-undefined.com",
|
||||
expiry: undefined,
|
||||
lastExpiryNotificationSent: null
|
||||
};
|
||||
|
||||
mock.method(DomainExpiry, "forMonitor", async () => mockDomain);
|
||||
mock.method(DomainExpiry, "findByDomainNameOrCreate", async () => mockDomain);
|
||||
|
||||
const hook = {
|
||||
"port": 3013,
|
||||
"url": "should-not-be-called-undefined"
|
||||
};
|
||||
|
||||
const monTest = {
|
||||
type: "http",
|
||||
url: "https://test-undefined.com",
|
||||
domainExpiryNotification: true
|
||||
};
|
||||
|
||||
const notif = {
|
||||
name: "TestUndefinedExpiry",
|
||||
config: JSON.stringify({
|
||||
@@ -159,7 +337,7 @@ describe("Domain Expiry", () => {
|
||||
// Race between sendNotifications and mockWebhook timeout
|
||||
// If webhook is called, we fail. If it times out, we pass.
|
||||
const result = await Promise.race([
|
||||
DomainExpiry.sendNotifications(monTest, [ notif ]),
|
||||
DomainExpiry.sendNotifications("test-undefined.com", [ notif ]),
|
||||
mockWebhook(hook.port, hook.url, 500).then(() => {
|
||||
throw new Error("Webhook was called but should not have been for undefined expiry");
|
||||
}).catch((e) => {
|
||||
|
||||
Reference in New Issue
Block a user