mirror of
https://github.com/koichixD/uptime-kuma.git
synced 2026-09-04 17:23:21 +00:00
Merge branch 'master' into feature/umami-analytics-status-page
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
const { test } = require("node:test");
|
||||
|
||||
const assert = require("node:assert");
|
||||
|
||||
const { checkCertificateHostname } = require("../../server/util-server");
|
||||
|
||||
const testCert = `
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFCTCCA/GgAwIBAgISBEROD0/r+BjpW4TvWCcZYxjpMA0GCSqGSIb3DQEBCwUA
|
||||
MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD
|
||||
EwJSMzAeFw0yMzA5MDQxMjExMThaFw0yMzEyMDMxMjExMTdaMBQxEjAQBgNVBAMM
|
||||
CSouZWZmLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALywpmHr
|
||||
GOFlhw9CcW11fVloL6dceeUexbIwVd/gOt0/rIlgBViOGCh1pFYA/Essty4vXBzx
|
||||
cp6W4WurmwU6ZOJA0/T6rxnmsjxSdrHVGBGgW18HJ9IWqBl9MigjpRo9h4SlAPJq
|
||||
cAsiBfPhQ0oSe/8IqwgKA4HTvlcTf5/HKnbe0MyQt7WNILWHm+zpfLE0AmLVXxqA
|
||||
MNc/ynQDLTsWDZnqqri4MKOW1yOAMbUoAWSsNaagoGnZU4bg8uhu/2JTi/vdjl0g
|
||||
fTDOjsELc70cWekZ9Mv4ND4w3SEthotbMCCtZE5bUqcGzSm4pQEJ37kQ7xjJ0onT
|
||||
RRcuZI6/jDWzwZ0CAwEAAaOCAjUwggIxMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUE
|
||||
FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU
|
||||
hTqVTd8TZ2pknzGJtKw2JaIrPJAwHwYDVR0jBBgwFoAUFC6zF7dYVsuuUAlA5h+v
|
||||
nYsUwsYwVQYIKwYBBQUHAQEESTBHMCEGCCsGAQUFBzABhhVodHRwOi8vcjMuby5s
|
||||
ZW5jci5vcmcwIgYIKwYBBQUHMAKGFmh0dHA6Ly9yMy5pLmxlbmNyLm9yZy8wPwYD
|
||||
VR0RBDgwNoIJKi5lZmYub3JnghEqLnN0YWdpbmcuZWZmLm9yZ4IWd3d3Lmh0dHBz
|
||||
LXJ1bGVzZXRzLm9yZzATBgNVHSAEDDAKMAgGBmeBDAECATCCAQMGCisGAQQB1nkC
|
||||
BAIEgfQEgfEA7wB2ALc++yTfnE26dfI5xbpY9Gxd/ELPep81xJ4dCYEl7bSZAAAB
|
||||
imBRp0EAAAQDAEcwRQIhAMW3HZwWZWXPWfahH2pr/lxCcoSluHv2huAW6rlzU3zn
|
||||
AiAOzD/p8F3gT1bzDgdSW+X5WDBeU+EutRbHMSV+Cx0mZwB1AHoyjFTYty22IOo4
|
||||
4FIe6YQWcDIThU070ivBOlejUutSAAABimBRqRQAAAQDAEYwRAIgFXvRRZS3xx83
|
||||
XdTsnto5SxSnGi1+YfzYobMdV1yqHGACIDurLvkt58TwifUbyXflGZJmOMhcC2G1
|
||||
KUd29yCUjIahMA0GCSqGSIb3DQEBCwUAA4IBAQA6t2F3PKMLlb2A/JsQhPFUJLS3
|
||||
6cx+97dzROQLBdnUQIMxPkJBN/lltNdsVxJa4A3DMbrJOayefX2l8UIvFiEFVseF
|
||||
WrxbmXDF68fwhBKBgeqZ25/S8jEdP5PWYWXHgXvx0zRdhfe9vuba5WeFyz79cR7K
|
||||
t3bSyv6GMJ2z3qBkVFGHSeYakcxPWes3CNmGxInwZNBXA2oc7xuncFrjno/USzUI
|
||||
nEefDfF3H3jC+0iP3IpsK8orwgWz4lOkcMYdan733lSZuVJ6pm7C9phTV04NGF3H
|
||||
iPenGDCg1awOyRnvxNq1MtMDkR9AHwksukzwiYNexYjyvE2t0UzXhFXwazQ3
|
||||
-----END CERTIFICATE-----
|
||||
`;
|
||||
|
||||
test("Certificate and hostname match", () => {
|
||||
const result = checkCertificateHostname(testCert, "www.eff.org");
|
||||
assert.strictEqual(result, true);
|
||||
});
|
||||
|
||||
test("Certificate and hostname mismatch", () => {
|
||||
const result = checkCertificateHostname(testCert, "example.com");
|
||||
assert.strictEqual(result, false);
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
const dayjs = require("dayjs");
|
||||
const { SQL_DATETIME_FORMAT } = require("../../src/util");
|
||||
|
||||
dayjs.extend(require("dayjs/plugin/utc"));
|
||||
dayjs.extend(require("dayjs/plugin/customParseFormat"));
|
||||
|
||||
/**
|
||||
* Tests for maintenance date formatting to ensure compatibility with MariaDB/MySQL.
|
||||
* Issue: MariaDB rejects ISO format dates like '2025-12-19T01:04:02.129Z'
|
||||
* Fix: Use SQL_DATETIME_FORMAT ('YYYY-MM-DD HH:mm:ss') instead of toISOString()
|
||||
*/
|
||||
test("Maintenance Date Format - MariaDB Compatibility", async (t) => {
|
||||
|
||||
await t.test("SQL_DATETIME_FORMAT constant should match MariaDB format", async () => {
|
||||
assert.strictEqual(SQL_DATETIME_FORMAT, "YYYY-MM-DD HH:mm:ss");
|
||||
});
|
||||
|
||||
await t.test("Format date using SQL_DATETIME_FORMAT", async () => {
|
||||
const current = dayjs.utc("2025-12-19T01:04:02.129Z");
|
||||
const sqlFormat = current.utc().format(SQL_DATETIME_FORMAT);
|
||||
|
||||
assert.strictEqual(sqlFormat, "2025-12-19 01:04:02");
|
||||
});
|
||||
|
||||
await t.test("SQL format should not contain ISO markers (T, Z)", async () => {
|
||||
const current = dayjs.utc("2025-12-19T01:04:02.129Z");
|
||||
const sqlFormat = current.utc().format(SQL_DATETIME_FORMAT);
|
||||
|
||||
assert.strictEqual(sqlFormat.includes("T"), false, "SQL format should not contain 'T'");
|
||||
assert.strictEqual(sqlFormat.includes("Z"), false, "SQL format should not contain 'Z'");
|
||||
});
|
||||
|
||||
await t.test("SQL format should match YYYY-MM-DD HH:mm:ss pattern", async () => {
|
||||
const current = dayjs.utc("2025-12-19T01:04:02.129Z");
|
||||
const sqlFormat = current.utc().format(SQL_DATETIME_FORMAT);
|
||||
const sqlDateTimeRegex = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
|
||||
|
||||
assert.strictEqual(sqlDateTimeRegex.test(sqlFormat), true);
|
||||
});
|
||||
|
||||
await t.test("Parse SQL datetime back to dayjs preserves timestamp", async () => {
|
||||
const originalDate = dayjs.utc("2025-12-19T01:04:02.000Z");
|
||||
const sqlFormat = originalDate.utc().format(SQL_DATETIME_FORMAT);
|
||||
const parsedDate = dayjs.utc(sqlFormat, SQL_DATETIME_FORMAT);
|
||||
|
||||
assert.strictEqual(parsedDate.unix(), originalDate.unix());
|
||||
});
|
||||
|
||||
await t.test("Edge case: midnight timestamp", async () => {
|
||||
const midnight = dayjs.utc("2025-01-01T00:00:00.000Z");
|
||||
const sqlFormat = midnight.utc().format(SQL_DATETIME_FORMAT);
|
||||
|
||||
assert.strictEqual(sqlFormat, "2025-01-01 00:00:00");
|
||||
});
|
||||
|
||||
await t.test("Edge case: end of day timestamp", async () => {
|
||||
const endOfDay = dayjs.utc("2025-12-31T23:59:59.999Z");
|
||||
const sqlFormat = endOfDay.utc().format(SQL_DATETIME_FORMAT);
|
||||
|
||||
assert.strictEqual(sqlFormat, "2025-12-31 23:59:59");
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,133 @@
|
||||
const { describe, test } = require("node:test");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { GenericContainer, Wait } = require("testcontainers");
|
||||
|
||||
describe("Database Migration - Optimize Important Indexes", () => {
|
||||
test("SQLite: All migrations run successfully", async () => {
|
||||
const testDbPath = path.join(__dirname, "../../data/test-migration.db");
|
||||
const testDbDir = path.dirname(testDbPath);
|
||||
|
||||
// Ensure data directory exists
|
||||
if (!fs.existsSync(testDbDir)) {
|
||||
fs.mkdirSync(testDbDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Clean up any existing test database
|
||||
if (fs.existsSync(testDbPath)) {
|
||||
fs.unlinkSync(testDbPath);
|
||||
}
|
||||
|
||||
// Use the same SQLite driver as the project
|
||||
const Dialect = require("knex/lib/dialects/sqlite3/index.js");
|
||||
Dialect.prototype._driver = () => require("@louislam/sqlite3");
|
||||
|
||||
const knex = require("knex");
|
||||
const db = knex({
|
||||
client: Dialect,
|
||||
connection: {
|
||||
filename: testDbPath
|
||||
},
|
||||
useNullAsDefault: true,
|
||||
});
|
||||
|
||||
// Setup R (redbean) with knex instance like production code does
|
||||
const { R } = require("redbean-node");
|
||||
R.setup(db);
|
||||
|
||||
try {
|
||||
// Use production code to initialize SQLite tables (like first run)
|
||||
const { createTables } = require("../../db/knex_init_db.js");
|
||||
await createTables();
|
||||
|
||||
// Run all migrations like production code does
|
||||
await R.knex.migrate.latest({
|
||||
directory: path.join(__dirname, "../../db/knex_migrations")
|
||||
});
|
||||
|
||||
// Test passes if migrations complete successfully without errors
|
||||
|
||||
} finally {
|
||||
// Clean up
|
||||
await R.knex.destroy();
|
||||
if (fs.existsSync(testDbPath)) {
|
||||
fs.unlinkSync(testDbPath);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test(
|
||||
"MariaDB: All migrations run successfully",
|
||||
{
|
||||
skip:
|
||||
!!process.env.CI &&
|
||||
(process.platform !== "linux" || process.arch !== "x64"),
|
||||
},
|
||||
async () => {
|
||||
// Start MariaDB container (using MariaDB 12 to match current production)
|
||||
const mariadbContainer = await new GenericContainer("mariadb:12")
|
||||
.withEnvironment({
|
||||
"MYSQL_ROOT_PASSWORD": "root",
|
||||
"MYSQL_DATABASE": "kuma_test",
|
||||
"MYSQL_USER": "kuma",
|
||||
"MYSQL_PASSWORD": "kuma"
|
||||
})
|
||||
.withExposedPorts(3306)
|
||||
.withWaitStrategy(Wait.forLogMessage("ready for connections", 2))
|
||||
.withStartupTimeout(120000)
|
||||
.start();
|
||||
|
||||
// Wait a bit more to ensure MariaDB is fully ready
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
const knex = require("knex");
|
||||
const knexInstance = knex({
|
||||
client: "mysql2",
|
||||
connection: {
|
||||
host: mariadbContainer.getHost(),
|
||||
port: mariadbContainer.getMappedPort(3306),
|
||||
user: "kuma",
|
||||
password: "kuma",
|
||||
database: "kuma_test",
|
||||
connectTimeout: 60000,
|
||||
},
|
||||
pool: {
|
||||
min: 0,
|
||||
max: 10,
|
||||
acquireTimeoutMillis: 60000,
|
||||
idleTimeoutMillis: 60000,
|
||||
},
|
||||
});
|
||||
|
||||
// Setup R (redbean) with knex instance like production code does
|
||||
const { R } = require("redbean-node");
|
||||
R.setup(knexInstance);
|
||||
|
||||
try {
|
||||
// Use production code to initialize MariaDB tables
|
||||
const { createTables } = require("../../db/knex_init_db.js");
|
||||
await createTables();
|
||||
|
||||
// Run all migrations like production code does
|
||||
await R.knex.migrate.latest({
|
||||
directory: path.join(__dirname, "../../db/knex_migrations")
|
||||
});
|
||||
|
||||
// Test passes if migrations complete successfully without errors
|
||||
|
||||
} finally {
|
||||
// Clean up
|
||||
try {
|
||||
await R.knex.destroy();
|
||||
} catch (e) {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
try {
|
||||
await mariadbContainer.stop();
|
||||
} catch (e) {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
const { describe, test } = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
const { PostgreSqlContainer } = require("@testcontainers/postgresql");
|
||||
const { PostgresMonitorType } = require("../../server/monitor-types/postgres");
|
||||
const { UP, PENDING } = require("../../src/util");
|
||||
|
||||
describe(
|
||||
"Postgres Single Node",
|
||||
{
|
||||
skip:
|
||||
!!process.env.CI &&
|
||||
(process.platform !== "linux" || process.arch !== "x64"),
|
||||
},
|
||||
() => {
|
||||
test("Postgres is running", async () => {
|
||||
// The default timeout of 30 seconds might not be enough for the container to start
|
||||
const postgresContainer = await new PostgreSqlContainer(
|
||||
"postgres:latest"
|
||||
)
|
||||
.withStartupTimeout(60000)
|
||||
.start();
|
||||
const postgresMonitor = new PostgresMonitorType();
|
||||
const monitor = {
|
||||
databaseConnectionString: postgresContainer.getConnectionUri(),
|
||||
};
|
||||
|
||||
const heartbeat = {
|
||||
msg: "",
|
||||
status: PENDING,
|
||||
};
|
||||
|
||||
try {
|
||||
await postgresMonitor.check(monitor, heartbeat, {});
|
||||
assert.strictEqual(heartbeat.status, UP);
|
||||
} finally {
|
||||
postgresContainer.stop();
|
||||
}
|
||||
});
|
||||
|
||||
test("Postgres is not running", async () => {
|
||||
const postgresMonitor = new PostgresMonitorType();
|
||||
const monitor = {
|
||||
databaseConnectionString: "http://localhost:15432",
|
||||
};
|
||||
|
||||
const heartbeat = {
|
||||
msg: "",
|
||||
status: PENDING,
|
||||
};
|
||||
|
||||
// regex match any string
|
||||
const regex = /.+/;
|
||||
|
||||
await assert.rejects(
|
||||
postgresMonitor.check(monitor, heartbeat, {}),
|
||||
regex
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -185,4 +185,26 @@ describe("TCP Monitor", () => {
|
||||
regex
|
||||
);
|
||||
});
|
||||
test("XMPP server with valid certificate (STARTTLS)", async t => {
|
||||
const tcpMonitor = new TCPMonitorType();
|
||||
|
||||
const monitor = {
|
||||
hostname: "xmpp.earth",
|
||||
port: 5222,
|
||||
smtpSecurity: "starttls",
|
||||
isEnabledExpiryNotification: () => true,
|
||||
handleTlsInfo: async tlsInfo => {
|
||||
return tlsInfo;
|
||||
},
|
||||
};
|
||||
|
||||
const heartbeat = {
|
||||
msg: "",
|
||||
status: PENDING,
|
||||
};
|
||||
|
||||
await tcpMonitor.check(monitor, heartbeat, {});
|
||||
|
||||
assert.strictEqual(heartbeat.status, UP);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
@@ -53,7 +53,7 @@ test.describe("Monitor Form", () => {
|
||||
|
||||
const friendlyName = "Example DNS NS";
|
||||
await page.getByTestId("friendly-name-input").fill(friendlyName);
|
||||
await page.getByTestId("hostname-input").fill("example.com");
|
||||
await page.getByTestId("hostname-input").fill("kuma.pet");
|
||||
|
||||
const resolveTypeSelect = page.getByTestId("resolve-type-select");
|
||||
await resolveTypeSelect.click();
|
||||
@@ -65,9 +65,9 @@ test.describe("Monitor Form", () => {
|
||||
await page.getByTestId("add-condition-button").click();
|
||||
expect(await page.getByTestId("condition").count()).toEqual(2); // 2 explicitly added
|
||||
|
||||
await page.getByTestId("condition-value").nth(0).fill("a.iana-servers.net");
|
||||
await page.getByTestId("condition-value").nth(0).fill("carl.ns.cloudflare.com");
|
||||
await page.getByTestId("condition-and-or").nth(0).selectOption("or");
|
||||
await page.getByTestId("condition-value").nth(1).fill("b.iana-servers.net");
|
||||
await page.getByTestId("condition-value").nth(1).fill("jean.ns.cloudflare.com");
|
||||
|
||||
await screenshot(testInfo, page);
|
||||
await page.getByTestId("save-button").click();
|
||||
@@ -86,7 +86,7 @@ test.describe("Monitor Form", () => {
|
||||
|
||||
const friendlyName = "Example DNS NS";
|
||||
await page.getByTestId("friendly-name-input").fill(friendlyName);
|
||||
await page.getByTestId("hostname-input").fill("example.com");
|
||||
await page.getByTestId("hostname-input").fill("kuma.pet");
|
||||
|
||||
const resolveTypeSelect = page.getByTestId("resolve-type-select");
|
||||
await resolveTypeSelect.click();
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user