feat: Domain name expiry (#6413)

Co-authored-by: AiroPi <47398145+AiroPi@users.noreply.github.com>
Co-authored-by: Frank Elsinga <frank@elsinga.de>
This commit is contained in:
Shaan
2025-12-20 16:32:49 +00:00
committed by GitHub
co-authored by AiroPi Frank Elsinga
parent f3c76dbc6f
commit eb0b6cdb09
17 changed files with 926 additions and 14 deletions
+70
View File
@@ -0,0 +1,70 @@
process.env.UPTIME_KUMA_HIDE_LOG = [ "info_db", "info_server" ].join(",");
const test = require("node:test");
const assert = require("node:assert");
const DomainExpiry = require("../../server/model/domain_expiry");
const mockWebhook = require("../mock-webhook");
const TestDB = require("../mock-testdb");
const { R } = require("redbean-node");
const { Notification } = require("../../server/notification");
const { Settings } = require("../../server/settings");
const { setSetting } = require("../../server/util-server");
const testDb = new TestDB();
test("Domain Expiry", async (t) => {
await testDb.create();
Notification.init();
const monHttpCom = {
type: "http",
url: "https://www.google.com",
domainExpiryNotification: true
};
await t.test("Should get expiry date for .wiki with no A record", async () => {
const d = DomainExpiry.createByName("google.wiki");
assert.deepEqual(await d.getExpiryDate(), new Date("2026-11-26T23:59:59.000Z"));
});
await t.test("Should get expiration date for .com from RDAP", async () => {
const domain = await DomainExpiry.forMonitor(monHttpCom);
const expiryFromRdap = await domain.getExpiryDate(); // from RDAP
assert.deepEqual(expiryFromRdap, new Date("2028-09-14T04:00:00.000Z"));
});
await t.test("Should have expiration date cached in database", async () => {
await DomainExpiry.checkExpiry(monHttpCom); // RDAP -> Cache
const domain = await DomainExpiry.findByName("google.com");
assert(Date.now() - domain.lastCheck < 5 * 1000);
});
await t.test("Should trigger notify for expiring domain", async () => {
await DomainExpiry.findByName("google.com");
const hook = {
"port": 3010,
"url": "capture"
};
await setSetting("domainExpiryNotifyDays", [ 1, 2, 1500 ], "general");
const notif = R.convertToBean("notification", {
"config": JSON.stringify({
type: "webhook",
httpMethod: "post",
webhookContentType: "json",
webhookURL: `http://127.0.0.1:${hook.port}/${hook.url}`
}),
"active": 1,
"user_id": 1,
"name": "Testhook"
});
const manyDays = 1500;
setSetting("domainExpiryNotifyDays", [ 7, 14, manyDays ], "general");
const [ notifRet, data ] = await Promise.all([
DomainExpiry.sendNotifications(monHttpCom, [ notif ]),
mockWebhook(hook.port, hook.url)
]);
assert.equal(notifRet, manyDays);
assert.match(data.msg, /will expire in/);
});
}).finally(() => {
setTimeout(async () => {
Settings.stopCacheCleaner();
await testDb.destroy();
}, 200);
});
+18
View File
@@ -0,0 +1,18 @@
const test = require("node:test");
const assert = require("node:assert");
const { getDaysRemaining, getDaysBetween } = require("../../server/util-server");
test("Test getDaysBetween", async (t) => {
let days = getDaysBetween(new Date(2025, 9, 7), new Date(2025, 9, 10));
assert.strictEqual(days, 3);
days = getDaysBetween(new Date(2024, 9, 7), new Date(2025, 9, 10));
assert.strictEqual(days, 368);
});
test("Test getDaysRemaining", async (t) => {
let days = getDaysRemaining(new Date(2025, 9, 7), new Date(2025, 9, 10));
assert.strictEqual(days, 3);
days = getDaysRemaining(new Date(2025, 9, 10), new Date(2025, 9, 7));
assert.strictEqual(days, -3);
});
+27
View File
@@ -0,0 +1,27 @@
const { sync: rimrafSync } = require("rimraf");
const Database = require("../server/database");
class TestDB {
dataDir;
constructor(dir = "./data/test") {
this.dataDir = dir;
}
async create() {
Database.initDataDir({ "data-dir": this.dataDir });
Database.dbConfig = {
type: "sqlite"
};
Database.writeDBConfig(Database.dbConfig);
await Database.connect(true);
await Database.patch();
}
async destroy() {
await Database.close();
this.dataDir && rimrafSync(this.dataDir);
}
}
module.exports = TestDB;
+28
View File
@@ -0,0 +1,28 @@
const express = require("express");
const bodyParser = require("body-parser");
/**
* @param {number} port Port number
* @param {string} url Webhook URL
* @param {number} timeout Timeout
* @returns {Promise<object>} Webhook data
*/
async function mockWebhook(port, url, timeout = 2500) {
return new Promise((resolve, reject) => {
const app = express();
const tmo = setTimeout(() => {
server.close();
reject({ reason: "Timeout" });
}, timeout);
app.use(bodyParser.json()); // Middleware to parse JSON bodies
app.post(`/${url}`, (req, res) => {
res.status(200).send("OK");
server.close();
tmo && clearTimeout(tmo);
resolve(req.body);
});
const server = app.listen(port);
});
}
module.exports = mockWebhook;