Files
ReinLoopTest/server/test/server.test.js
T
2026-08-03 16:35:23 +08:00

1012 lines
41 KiB
JavaScript

const assert = require("node:assert/strict");
const crypto = require("node:crypto");
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
const { after, before, test } = require("node:test");
const { createApp } = require("../src/app");
const { JsonStore } = require("../src/store");
let baseUrl;
let dataDirectory;
let server;
let licensePrivateKey;
let licensePublicKeyPath;
async function post(payload) {
const response = await fetch(baseUrl, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(payload)
});
assert.equal(response.status, 200);
return response.json();
}
function signLicense(payload) {
const payloadBase64 = Buffer.from(JSON.stringify(payload)).toString("base64");
const signature = crypto.sign("sha256", Buffer.from(payloadBase64), {
key: licensePrivateKey,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
saltLength: crypto.constants.RSA_PSS_SALTLEN_MAX
});
return `${payloadBase64}|${signature.toString("base64")}`;
}
before(async () => {
dataDirectory = await fs.promises.mkdtemp(path.join(os.tmpdir(), "reinloop-server-"));
const keyPair = crypto.generateKeyPairSync("rsa", { modulusLength: 2048 });
licensePrivateKey = keyPair.privateKey;
licensePublicKeyPath = path.join(dataDirectory, "license-public.pem");
await fs.promises.writeFile(licensePublicKeyPath, keyPair.publicKey.export({ type: "spki", format: "pem" }));
const store = new JsonStore(dataDirectory);
await store.initialize();
const app = createApp({ store, adminToken: "test-token", licensePublicKeyPath });
await new Promise((resolve) => {
server = app.listen(0, "127.0.0.1", resolve);
});
baseUrl = `http://127.0.0.1:${server.address().port}`;
});
after(async () => {
await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
await fs.promises.rm(dataDirectory, { recursive: true, force: true });
});
test("health endpoint reports ready", async () => {
const response = await fetch(`${baseUrl}/health`);
assert.deepEqual(await response.json(), { success: true, service: "reinloop-server" });
});
test("root endpoint accepts API requests", async () => {
const response = await fetch(baseUrl, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ type: "listOrganizations", adminToken: "test-token" })
});
assert.equal(response.status, 200);
assert.deepEqual(await response.json(), { success: true, companies: [] });
});
test("legacy /api endpoint is unavailable", async () => {
const response = await fetch(`${baseUrl}/api`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ type: "listOrganizations", adminToken: "test-token" })
});
assert.equal(response.status, 404);
});
test("legacy data_record endpoint and uploadUserInfo type are unavailable", async () => {
const legacyRoute = await fetch(`${baseUrl}/data_record`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ type: "listOrganizations", adminToken: "test-token" })
});
assert.equal(legacyRoute.status, 404);
const legacyType = await post({
type: "uploadUserInfo", _id: "legacy", username: "legacy",
issued: "2026-07-25 12:00", expiry: "2027-07-25 12:00", license: "legacy"
});
assert.equal(legacyType.success, false);
assert.match(legacyType.errMsg, /无效/);
});
test("existing two-step client flow uploads, lists, and downloads a file", async () => {
const issued = await post({ type: "uploadDataFile", fileName: "result.csv", folder: "customer/line-1/ind_data" });
assert.equal(issued.success, true);
assert.ok(issued.uploadMetadata.authorization);
const form = new FormData();
form.append("key", issued.uploadMetadata.cosFileId);
form.append("Signature", issued.uploadMetadata.authorization);
form.append("x-cos-security-token", issued.uploadMetadata.token);
form.append("x-cos-meta-fileid", issued.uploadMetadata.fileId);
form.append("file", new Blob(["time,pressure\n0,10\n"], { type: "text/csv" }), "result.csv");
const uploaded = await fetch(issued.uploadMetadata.url, { method: "POST", body: form });
assert.equal(uploaded.status, 204);
const listed = await post({ type: "listModels", folder: "customer/line-1/ind_data" });
assert.deepEqual(listed.files, ["result.csv"]);
assert.equal(listed.fileList[0].fileID, issued.fileID);
const directDownload = await fetch(`${baseUrl}/files/${encodeURIComponent(issued.fileID)}`);
assert.equal(directDownload.status, 403);
const rejectedDownload = await post({ type: "downloadModel", fileID: issued.fileID });
assert.equal(rejectedDownload.success, false);
const download = await post({
type: "downloadModel", fileID: issued.fileID, adminToken: "test-token"
});
const downloaded = await fetch(download.url);
assert.equal(await downloaded.text(), "time,pressure\n0,10\n");
assert.equal((await fetch(download.url)).status, 403);
});
test("admin lists, downloads, and deletes only the selected device control data", async () => {
async function uploadControlData(deviceId, fileName, content) {
const issued = await post({
type: "uploadDataFile", fileName, folder: `${deviceId}/data_record/run-1`
});
assert.equal(issued.success, true);
const form = new FormData();
form.append("file", new Blob([content]), fileName);
assert.equal((await fetch(issued.uploadMetadata.url, { method: "POST", body: form })).status, 204);
return issued;
}
const deviceId = "control-co/line-1";
const first = await uploadControlData(deviceId, "episode_part1.pkl", "part-1");
const second = await uploadControlData(deviceId, "episode_manifest.json", '{"parts":1}');
await uploadControlData("other-co/line-2", "other.pkl", "other");
const rejectedList = await post({ type: "listControlFiles", deviceId });
assert.equal(rejectedList.success, false);
const listed = await post({
type: "listControlFiles", deviceId, page: 1, pageSize: 500, adminToken: "test-token"
});
assert.equal(listed.total, 2);
assert.equal(listed.pageSize, 100);
assert.deepEqual(new Set(listed.files.map((item) => item.fileID)), new Set([first.fileID, second.fileID]));
const forbiddenDownload = await post({
type: "getControlFileDownload", fileID: "local://ReinLoop_GUI/other-co/line-2/ind_data/result.csv",
adminToken: "test-token"
});
assert.equal(forbiddenDownload.success, false);
const download = await post({
type: "getControlFileDownload", fileID: second.fileID, adminToken: "test-token"
});
assert.equal(download.success, true);
assert.equal(download.size, Buffer.byteLength('{"parts":1}'));
assert.match(download.url, /\/downloads\//);
assert.equal(await (await fetch(download.url)).text(), '{"parts":1}');
assert.equal((await fetch(download.url)).status, 403);
const forbiddenDelete = await post({
type: "deleteControlFile", fileID: "model://control-co/line-1/controller.bin", adminToken: "test-token"
});
assert.equal(forbiddenDelete.success, false);
const deleted = await post({ type: "deleteControlFile", fileID: first.fileID, adminToken: "test-token" });
assert.deepEqual(deleted, { success: true, deletedCount: 1 });
assert.equal((await post({
type: "deleteControlFile", fileID: first.fileID, adminToken: "test-token"
})).success, false);
});
test("model uploads are stored under data/models/company/line", async () => {
const issued = await post({
type: "issueModelUpload", deviceId: "company-a/line-1",
fileName: "controller.bin", adminToken: "test-token"
});
assert.equal(issued.fileID, "model://company-a/line-1/controller.bin");
const form = new FormData();
form.append("file", new Blob(["model-content"]), "controller.bin");
assert.equal((await fetch(issued.uploadMetadata.url, { method: "POST", body: form })).status, 204);
assert.equal(
await fs.promises.readFile(path.join(dataDirectory, "models", "company-a", "line-1", "controller.bin"), "utf8"),
"model-content"
);
const listed = await post({ type: "listModels", folder: "company-a/line-1/model_config" });
assert.equal(listed.fileList[0].fileID, issued.fileID);
});
test("model upload supports renaming while preserving the original file name", async () => {
const issued = await post({
type: "issueModelUpload", deviceId: "company-a/rename-line",
fileName: "controller-original.bin", modelName: "pressure-controller-v2.bin",
adminToken: "test-token"
});
assert.equal(issued.success, true);
assert.equal(issued.fileName, "pressure-controller-v2.bin");
assert.equal(issued.originalFileName, "controller-original.bin");
assert.equal(issued.fileID, "model://company-a/rename-line/pressure-controller-v2.bin");
const form = new FormData();
form.append("file", new Blob(["renamed-model"]), "controller-original.bin");
assert.equal((await fetch(issued.uploadMetadata.url, { method: "POST", body: form })).status, 204);
const listed = await post({
type: "listModels", folder: "company-a/rename-line/model_config"
});
assert.deepEqual(listed.files, ["pressure-controller-v2.bin"]);
assert.equal(listed.fileList[0].fileName, "pressure-controller-v2.bin");
assert.equal(listed.fileList[0].originalFileName, "controller-original.bin");
assert.equal(
await fs.promises.readFile(path.join(dataDirectory, "models", "company-a", "rename-line", "pressure-controller-v2.bin"), "utf8"),
"renamed-model"
);
const invalid = await post({
type: "issueModelUpload", deviceId: "company-a/rename-line",
fileName: "controller.bin", modelName: "controller.zip",
adminToken: "test-token"
});
assert.equal(invalid.success, false);
assert.match(invalid.errMsg, /扩展名/);
});
test("model upload requires explicit overwrite for an existing file", async () => {
const request = {
type: "issueModelUpload", deviceId: "company-a/overwrite-line",
fileName: "controller.bin", adminToken: "test-token"
};
const issued = await post(request);
const firstForm = new FormData();
firstForm.append("file", new Blob(["first-version"]), "controller.bin");
assert.equal((await fetch(issued.uploadMetadata.url, { method: "POST", body: firstForm })).status, 204);
const conflict = await post(request);
assert.equal(conflict.success, false);
assert.equal(conflict.conflict, true);
assert.equal(conflict.existing.fileID, issued.fileID);
const replacement = await post({ ...request, overwrite: true });
assert.equal(replacement.success, true);
const replacementForm = new FormData();
replacementForm.append("file", new Blob(["second-version"]), "controller.bin");
assert.equal((await fetch(replacement.uploadMetadata.url, {
method: "POST", body: replacementForm
})).status, 204);
assert.equal(
await fs.promises.readFile(path.join(dataDirectory, "models", "company-a", "overwrite-line", "controller.bin"), "utf8"),
"second-version"
);
const deleted = await post({
type: "deleteModel", fileID: replacement.fileID, adminToken: "test-token"
});
assert.equal(deleted.success, true);
assert.equal(deleted.deletedCount, 1);
const listed = await post({
type: "listModels", folder: "company-a/overwrite-line/model_config"
});
assert.deepEqual(listed.fileList, []);
await assert.rejects(fs.promises.access(
path.join(dataDirectory, "models", "company-a", "overwrite-line", "controller.bin")
), { code: "ENOENT" });
});
test("panel consumes uploaded device files from an inbox without scanning folders", async () => {
const deviceId = "panel-company/panel-line";
const issued = await post({
type: "uploadDataFile", fileName: "result_20260724_120000.csv",
folder: `${deviceId}/ind_data`
});
const form = new FormData();
form.append("file", new Blob(["t,u,p\n0,10,20\n"], { type: "text/csv" }), issued.fileID);
assert.equal((await fetch(issued.uploadMetadata.url, { method: "POST", body: form })).status, 204);
const beforeRegistration = await post({
type: "getPendingPanelFile", deviceId, adminToken: "test-token"
});
assert.equal(beforeRegistration.pending, false);
await post({
type: "registerIdentificationResult", deviceId,
runId: "result_20260724_120000.csv"
});
const pending = await post({
type: "getPendingPanelFile", deviceId, adminToken: "test-token"
});
assert.equal(pending.pending, true);
assert.equal(pending.fileName, "result_20260724_120000.csv");
assert.equal(await (await fetch(pending.url)).text(), "t,u,p\n0,10,20\n");
assert.equal((await post({
type: "ackPanelFile", deviceId, fileID: pending.fileID,
adminToken: "test-token"
})).deleted, 1);
assert.equal((await post({
type: "getPendingPanelFile", deviceId, adminToken: "test-token"
})).pending, false);
const jsonIssued = await post({
type: "uploadDataFile", fileName: "travel_stability_pressures_20260724_120000.json",
folder: `${deviceId}/ind_data`
});
const jsonForm = new FormData();
jsonForm.append("file", new Blob(['{"stable_pressures":[]}'], {
type: "application/json"
}), jsonIssued.fileID);
assert.equal((await fetch(jsonIssued.uploadMetadata.url, {
method: "POST", body: jsonForm
})).status, 204);
const jsonPending = await post({
type: "getPendingPanelFile", deviceId, adminToken: "test-token"
});
assert.equal(jsonPending.pending, true);
assert.equal(jsonPending.mediaType, "json");
const rejectedHistory = await post({ type: "listIdentificationFiles", deviceId });
assert.equal(rejectedHistory.success, false);
const history = await post({
type: "listIdentificationFiles", deviceId, adminToken: "test-token"
});
assert.equal(history.total, 2);
const csvHistory = history.files.find((file) => file.fileID === pending.fileID);
assert.equal(csvHistory.status, "processed");
assert.ok(csvHistory.processedAt);
assert.ok(csvHistory.expiresAt);
const rejectedHistoryDownload = await post({
type: "getIdentificationFileDownload", fileID: pending.fileID
});
assert.equal(rejectedHistoryDownload.success, false);
const historyDownload = await post({
type: "getIdentificationFileDownload", fileID: pending.fileID,
adminToken: "test-token"
});
assert.equal(await (await fetch(historyDownload.url)).text(), "t,u,p\n0,10,20\n");
const rejectedDelete = await post({
type: "deleteIdentificationFile", fileID: jsonPending.fileID
});
assert.equal(rejectedDelete.success, false);
const deleted = await post({
type: "deleteIdentificationFile", fileID: jsonPending.fileID,
adminToken: "test-token"
});
assert.equal(deleted.deletedCount, 1);
assert.equal((await fetch(jsonPending.url)).status, 404);
});
test("expired processed identification files are purged without affecting pending files", async () => {
const deviceId = "retention-company/retention-line";
const processedIssued = await post({
type: "uploadDataFile", fileName: "processed.csv", folder: `${deviceId}/ind_data`
});
const processedForm = new FormData();
processedForm.append("file", new Blob(["processed"]), "processed.csv");
assert.equal((await fetch(processedIssued.uploadMetadata.url, {
method: "POST", body: processedForm
})).status, 204);
await post({
type: "ackPanelFile", deviceId, fileID: processedIssued.fileID,
adminToken: "test-token"
});
const pendingIssued = await post({
type: "uploadDataFile", fileName: "pending.json", folder: `${deviceId}/ind_data`
});
const pendingForm = new FormData();
pendingForm.append("file", new Blob(["{}"]), "pending.json");
assert.equal((await fetch(pendingIssued.uploadMetadata.url, {
method: "POST", body: pendingForm
})).status, 204);
const store = new JsonStore(dataDirectory);
await store.update((database) => {
const record = database.identificationFiles.find((item) => item.fileID === processedIssued.fileID);
record.expiresAt = new Date(Date.now() - 1000).toISOString();
});
const history = await post({
type: "listIdentificationFiles", deviceId, adminToken: "test-token"
});
assert.deepEqual(history.files.map((file) => file.fileID), [pendingIssued.fileID]);
assert.equal(history.files[0].status, "pending");
assert.equal(await fs.promises.access(
path.join(dataDirectory, "files", "ReinLoop_GUI", deviceId, "ind_data", "processed.csv")
).then(() => true, () => false), false);
});
test("identification feedback supports register, publish, poll, and acknowledge", async () => {
assert.equal((await post({
type: "registerIdentificationResult", deviceId: "customer-a/line-a", runId: "run-1"
})).success, true);
assert.deepEqual(await post({
type: "getIdentificationFeedback", deviceId: "customer-a/line-a", runId: "run-1"
}), { success: true, ready: false });
const published = await post({
type: "setIdentificationFeedback", deviceId: "customer-a/line-a", runId: "run-1",
result: 1, adminToken: "test-token"
});
assert.equal(published.result, 1);
assert.equal((await post({
type: "getIdentificationFeedback", deviceId: "customer-a/line-a", runId: "run-1"
})).result, 1);
assert.equal((await post({
type: "ackIdentificationFeedback", deviceId: "customer-a/line-a", runId: "run-1"
})).deleted, 1);
});
test("control panel publishes the device identification CSV consumed by ReinLoop", async () => {
const parameters = {
q_in_val: 91, dt: 0.1, n_order: 8, t_c: 2.5,
levels: [10, 20, 30, 40, 50, 60, 70, 80],
dead_area: 0, xa_full: 1000, V_val: 1, repeat: 2
};
const issued = await post({
type: "publishIdentificationConfig", deviceId: "customer-a/line-a",
parameters, adminToken: "test-token"
});
assert.equal(issued.success, true);
const invalid = await post({
type: "publishIdentificationConfig", deviceId: "customer-a/line-a",
parameters: { ...parameters, levels: [1000, 900, 800] },
adminToken: "test-token"
});
assert.equal(invalid.success, false);
assert.match(invalid.errMsg, /levels/);
const csv = [
"parameter,value",
"q_in_val,91",
"dt,0.1",
"n_order,8",
"t_c,2.5",
'levels,"10,20,30,40,50,60,70,80"',
"dead_area,0",
"xa_full,1000",
"V_val,1",
"repeat,2",
""
].join("\n");
const form = new FormData();
form.append("file", new Blob([csv], { type: "text/csv" }), "identification_config.csv");
assert.equal((await fetch(issued.uploadMetadata.url, {
method: "POST", body: form
})).status, 204);
const available = await post({
type: "getIdentificationConfig", deviceId: "customer-a/line-a"
});
assert.equal(available.success, true);
assert.equal(
available.cloudPath,
"ReinLoop_GUI/customer-a/line-a/identification_config/identification_config.csv"
);
assert.equal(await (await fetch(available.url)).text(), csv);
});
test("volume publishing becomes readable only after the file upload completes", async () => {
const parameters = {
q_in_val: 91, dt: 0.1, xa_full: 1000, p_max: 200,
fit_low: 50, fit_high: 200, T_delta: 30, num_runs: 6
};
const issued = await post({
type: "publishVolumeConfig", parameters, adminToken: "test-token"
});
const beforeUpload = await post({ type: "getFunctionConfig", configType: "volume" });
assert.equal(beforeUpload.notFound, true);
const form = new FormData();
form.append("file", new Blob([JSON.stringify(parameters)], {
type: "application/json"
}), "volume_config.json");
assert.equal((await fetch(issued.uploadMetadata.url, {
method: "POST", body: form
})).status, 204);
const published = await post({ type: "getFunctionConfig", configType: "volume" });
assert.equal(published.success, true);
assert.deepEqual(published.parameters, parameters);
});
test("volume request accepts only the file uploaded for that request", async () => {
const request = await post({ type: "createVolumeConfigRequest", deviceId: "customer-a/line-a" });
const folder = `customer-a/line-a/volume_config_requests/${request.requestId}`;
const issued = await post({ type: "uploadDataFile", fileName: "volume_measurement.json", folder });
const form = new FormData();
form.append("file", new Blob(["{\"num_runs\":2}"], { type: "application/json" }), "volume_measurement.json");
assert.equal((await fetch(issued.uploadMetadata.url, { method: "POST", body: form })).status, 204);
const submitted = await post({
type: "submitVolumeConfigFile", deviceId: "customer-a/line-a", requestId: request.requestId,
fileID: issued.fileID, fileName: "volume_measurement.json"
});
assert.equal(submitted.success, true);
const ready = await post({
type: "getVolumeConfigRequest", deviceId: "customer-a/line-a", requestId: request.requestId
});
assert.equal(ready.ready, true);
assert.equal(
ready.cloudPath,
`ReinLoop_GUI/customer-a/line-a/volume_config_requests/${request.requestId}/volume_measurement.json`
);
assert.deepEqual(await (await fetch(ready.url)).json(), { num_runs: 2 });
});
test("panel notifications and volume configuration stay isolated by device", async () => {
const deviceId = "notify-co/line-1";
const otherDeviceId = "notify-co/line-2";
const request = await post({ type: "createVolumeConfigRequest", deviceId });
const notification = await post({
type: "getPendingPanelNotification", deviceId, adminToken: "test-token"
});
assert.equal(notification.pending, true);
assert.equal(notification.notification.type, "volume_request_started");
assert.equal(notification.notification.requestId, request.requestId);
assert.equal((await post({
type: "getPendingPanelNotification", deviceId: otherDeviceId, adminToken: "test-token"
})).pending, false);
const folder = `${deviceId}/volume_config_requests/${request.requestId}`;
const issued = await post({ type: "uploadDataFile", fileName: "volume_measurement.json", folder });
const form = new FormData();
form.append("file", new Blob(['{"num_runs":2}'], { type: "application/json" }), "volume_measurement.json");
assert.equal((await fetch(issued.uploadMetadata.url, { method: "POST", body: form })).status, 204);
assert.equal((await post({
type: "submitVolumeConfigFile", deviceId, requestId: request.requestId,
fileID: issued.fileID, fileName: "volume_measurement.json"
})).success, true);
const config = await post({ type: "getVolumeConfigFile", deviceId, adminToken: "test-token" });
assert.equal(config.found, true);
assert.equal(config.fileID, issued.fileID);
assert.equal((await post({
type: "getVolumeConfigFile", deviceId: otherDeviceId, adminToken: "test-token"
})).found, false);
assert.equal((await post({
type: "ackPanelNotification", deviceId, notificationId: notification.notification.notificationId,
adminToken: "test-token"
})).success, true);
const duplicatedAck = await post({
type: "ackPanelNotification", deviceId, notificationId: notification.notification.notificationId,
adminToken: "test-token"
});
assert.equal(duplicatedAck.success, true);
assert.equal(duplicatedAck.idempotent, true);
assert.equal(duplicatedAck.deleted, 0);
async function uploadResult(folderName, fileName, content) {
const result = await post({ type: "uploadDataFile", fileName, folder: `${deviceId}/${folderName}` });
const uploadForm = new FormData();
uploadForm.append("file", new Blob([content]), fileName);
assert.equal((await fetch(result.uploadMetadata.url, { method: "POST", body: uploadForm })).status, 204);
}
await uploadResult("V_config", "volume_result.json", '{"volume_L":1.2}');
const volumeResult = await post({
type: "getPendingPanelNotification", deviceId, adminToken: "test-token"
});
assert.equal(volumeResult.notification.type, "volume_result_ready");
assert.ok(volumeResult.notification.fileID);
await post({
type: "ackPanelNotification", deviceId, notificationId: volumeResult.notification.notificationId,
adminToken: "test-token"
});
await uploadResult("ind_data", "identification_result.json", '{"stable_pressures":[]}');
const identificationResult = await post({
type: "getPendingPanelNotification", deviceId, adminToken: "test-token"
});
assert.equal(identificationResult.notification.type, "identification_result_ready");
});
test("admin manages companies and production lines with a stable device id", async () => {
const unauthorized = await post({
type: "createCompany", name: "未授权公司", code: "blocked"
});
assert.equal(unauthorized.success, false);
const company = await post({
type: "createCompany", name: "示例公司", code: "sample-co",
adminToken: "test-token"
});
assert.equal(company.success, true);
assert.equal(company.company.code, "sample-co");
const line = await post({
type: "createProductionLine", companyId: company.company.id,
name: "一号产线", code: "line-1", adminToken: "test-token"
});
assert.equal(line.success, true);
assert.equal(line.productionLine.deviceId, "sample-co/line-1");
const organizations = await post({
type: "listOrganizations", adminToken: "test-token"
});
assert.equal(organizations.companies.length, 1);
const listedLine = organizations.companies[0].productionLines[0];
assert.equal(listedLine.id, line.productionLine.id);
assert.equal(listedLine.online, false);
assert.equal(listedLine.lastSeenAt, null);
const heartbeat = await post({ type: "deviceHeartbeat", deviceId: line.productionLine.deviceId });
assert.equal(heartbeat.success, true);
assert.equal(heartbeat.deviceId, line.productionLine.deviceId);
assert.ok(heartbeat.lastSeenAt);
const onlineOrganizations = await post({ type: "listOrganizations", adminToken: "test-token" });
const onlineLine = onlineOrganizations.companies[0].productionLines[0];
assert.equal(onlineLine.online, true);
assert.equal(onlineLine.lastSeenAt, heartbeat.lastSeenAt);
const store = new JsonStore(dataDirectory);
await store.update((database) => {
const storedLine = database.productionLines.find((item) => item.id === line.productionLine.id);
storedLine.lastSeenAt = new Date(Date.now() - 30_001).toISOString();
});
const offlineOrganizations = await post({ type: "listOrganizations", adminToken: "test-token" });
assert.equal(offlineOrganizations.companies[0].productionLines[0].online, false);
const unknownDevice = await post({ type: "deviceHeartbeat", deviceId: "unknown-co/unknown-line" });
assert.equal(unknownDevice.success, false);
});
test("model write operations require an admin token", async () => {
const rejectedUpload = await post({
type: "uploadDataFile", fileName: "model.bin", folder: "company-a/line-a/model_config"
});
assert.equal(rejectedUpload.success, false);
const issued = await post({
type: "issueModelUpload", deviceId: "company-a/line-a", fileName: "model.bin",
adminToken: "test-token"
});
assert.equal(issued.success, true);
const form = new FormData();
form.append("file", new Blob(["model"], { type: "application/octet-stream" }), "model.bin");
assert.equal((await fetch(issued.uploadMetadata.url, { method: "POST", body: form })).status, 204);
const rejectedDelete = await post({ type: "deleteFile", fileID: issued.fileID });
assert.equal(rejectedDelete.success, false);
const deleted = await post({ type: "deleteModel", fileID: issued.fileID, adminToken: "test-token" });
assert.equal(deleted.deletedCount, 1);
});
test("issued licenses can be listed, validated, and revoked", async () => {
const company = await post({
type: "createCompany", name: "许可证公司", code: "licensed-co",
adminToken: "test-token"
});
const line = await post({
type: "createProductionLine", companyId: company.company.id,
name: "测试线", code: "test-line", adminToken: "test-token"
});
const licenseId = crypto.randomUUID();
const licensePayload = {
license_id: licenseId,
company_id: company.company.id,
production_line_id: line.productionLine.id,
customer: "许可证公司",
device_id: "licensed-co/test-line",
issued: "2026-07-25 12:00",
expiry: "2027-07-25 12:00",
features: "*"
};
const issued = await post({
type: "createLicense", licenseId,
companyId: company.company.id, productionLineId: line.productionLine.id,
customer: "许可证公司", issued: "2026-07-25 12:00",
expiry: "2027-07-25 12:00", features: "*",
license: signLicense(licensePayload), adminToken: "test-token"
});
assert.equal(issued.success, true);
assert.equal(issued.license.status, "active");
assert.equal(issued.license.deviceId, "licensed-co/test-line");
assert.equal("license" in issued.license, false);
const validation = await post({
type: "validateLicense", licenseId,
deviceId: "licensed-co/test-line"
});
assert.deepEqual(validation, {
success: true, valid: true, status: "active", licenseId
});
const listed = await post({ type: "listLicenses", adminToken: "test-token" });
assert.equal(listed.licenses.length, 1);
assert.equal(listed.licenses[0].customer, "许可证公司");
const revoked = await post({
type: "revokeLicense", licenseId,
reason: "合同终止", adminToken: "test-token"
});
assert.equal(revoked.license.status, "revoked");
assert.equal(revoked.license.revocationReason, "合同终止");
const rejected = await post({
type: "validateLicense", licenseId,
deviceId: "licensed-co/test-line"
});
assert.equal(rejected.valid, false);
assert.equal(rejected.status, "revoked");
});
test("revokeLicense returns actionable errors and accepts legacy request fields", async () => {
const invalidToken = await post({
type: "revokeLicense", licenseId: crypto.randomUUID(), adminToken: "wrong-token"
});
assert.deepEqual(invalidToken, {
success: false, errMsg: "B端管理令牌无效", errCode: "ADMIN_TOKEN_INVALID"
});
const missingLicenseId = await post({ type: "revokeLicense", licenseId: " ", adminToken: "test-token" });
assert.deepEqual(missingLicenseId, {
success: false, errMsg: "licenseId 不能为空", errCode: "LICENSE_ID_REQUIRED"
});
const notFound = await post({
type: "revokeLicense", licenseId: crypto.randomUUID(), adminToken: "test-token"
});
assert.deepEqual(notFound, {
success: false, errMsg: "许可证不存在", errCode: "LICENSE_NOT_FOUND"
});
const legacy = await post({
type: "revoke_license", license_id: " ", admin_token: "test-token"
});
assert.deepEqual(legacy, {
success: false, errMsg: "licenseId 不能为空", errCode: "LICENSE_ID_REQUIRED"
});
});
test("license creation verifies the signed payload and validates expiry in real time", async () => {
const company = await post({
type: "createCompany", name: "安全校验公司", code: "security-co", adminToken: "test-token"
});
const line = await post({
type: "createProductionLine", companyId: company.company.id, name: "安全线", code: "secure-line",
adminToken: "test-token"
});
const licenseId = crypto.randomUUID();
const payload = {
license_id: licenseId, company_id: company.company.id, production_line_id: line.productionLine.id,
customer: "安全校验公司", device_id: line.productionLine.deviceId,
issued: "2026-07-25 01:00", expiry: "2027-07-25 01:00", features: "*"
};
const request = {
type: "createLicense", licenseId, companyId: company.company.id, productionLineId: line.productionLine.id,
customer: payload.customer, issued: payload.issued, expiry: payload.expiry, features: "*",
license: signLicense(payload), adminToken: "test-token"
};
assert.equal((await post(request)).success, true);
assert.equal((await post(request)).idempotent, true);
const changed = await post({ ...request, customer: "已篡改客户" });
assert.equal(changed.success, false);
assert.match(changed.errMsg, /不一致/);
const invalidSignature = await post({
...request, licenseId: crypto.randomUUID(), license: `${request.license}x`
});
assert.equal(invalidSignature.success, false);
const expiredId = crypto.randomUUID();
const expiredPayload = {
...payload, license_id: expiredId, issued: "2020-01-01 00:00", expiry: "2020-01-02 00:00"
};
const expired = await post({
...request, licenseId: expiredId, issued: expiredPayload.issued, expiry: expiredPayload.expiry,
license: signLicense(expiredPayload)
});
assert.equal(expired.success, true);
assert.deepEqual(await post({
type: "validateLicense", licenseId: expiredId, deviceId: line.productionLine.deviceId
}), { success: true, valid: false, status: "expired", licenseId: expiredId });
});
test("deleteLicense requires prior revocation", async () => {
const suffix = crypto.randomUUID().slice(0, 8);
const company = await post({
type: "createCompany", name: `删除许可证公司-${suffix}`, code: `del-lic-${suffix}`,
adminToken: "test-token"
});
const line = await post({
type: "createProductionLine", companyId: company.company.id,
name: "许可证线", code: "line-1", adminToken: "test-token"
});
const licenseId = crypto.randomUUID();
const payload = {
license_id: licenseId,
company_id: company.company.id,
production_line_id: line.productionLine.id,
customer: company.company.name,
device_id: line.productionLine.deviceId,
issued: "2026-08-01 10:00",
expiry: "2028-08-01 10:00",
features: "*"
};
const created = await post({
type: "createLicense", licenseId,
companyId: company.company.id,
productionLineId: line.productionLine.id,
customer: payload.customer,
issued: payload.issued,
expiry: payload.expiry,
features: payload.features,
license: signLicense(payload),
adminToken: "test-token"
});
assert.equal(created.success, true);
const activeDelete = await post({ type: "deleteLicense", licenseId, adminToken: "test-token" });
assert.equal(activeDelete.success, false);
assert.equal(activeDelete.errCode, "LICENSE_ACTIVE");
const revoked = await post({
type: "revokeLicense", licenseId, reason: "测试删除", adminToken: "test-token"
});
assert.equal(revoked.success, true);
const deleted = await post({ type: "deleteLicense", licenseId, adminToken: "test-token" });
assert.deepEqual(deleted, { success: true, deletedLicenseId: licenseId });
const missing = await post({ type: "getLicense", licenseId, adminToken: "test-token" });
assert.equal(missing.success, false);
});
test("deleteProductionLine blocks active licenses and removes revoked data", async () => {
const suffix = crypto.randomUUID().slice(0, 8);
const company = await post({
type: "createCompany", name: `删除产线公司-${suffix}`, code: `del-line-${suffix}`,
adminToken: "test-token"
});
const line = await post({
type: "createProductionLine", companyId: company.company.id,
name: "待删产线", code: "line-1", adminToken: "test-token"
});
const modelIssued = await post({
type: "issueModelUpload", deviceId: line.productionLine.deviceId,
fileName: "controller.bin", adminToken: "test-token"
});
const modelForm = new FormData();
modelForm.append("file", new Blob(["model-bytes"]), "controller.bin");
assert.equal((await fetch(modelIssued.uploadMetadata.url, { method: "POST", body: modelForm })).status, 204);
const licenseId = crypto.randomUUID();
const payload = {
license_id: licenseId,
company_id: company.company.id,
production_line_id: line.productionLine.id,
customer: company.company.name,
device_id: line.productionLine.deviceId,
issued: "2026-08-01 10:00",
expiry: "2028-08-01 10:00",
features: "*"
};
await post({
type: "createLicense", licenseId,
companyId: company.company.id,
productionLineId: line.productionLine.id,
customer: payload.customer,
issued: payload.issued,
expiry: payload.expiry,
features: payload.features,
license: signLicense(payload),
adminToken: "test-token"
});
const blocked = await post({
type: "deleteProductionLine",
companyId: company.company.id,
productionLineId: line.productionLine.id,
adminToken: "test-token"
});
assert.equal(blocked.success, false);
assert.equal(blocked.errCode, "ACTIVE_LICENSES_PRESENT");
await post({ type: "revokeLicense", licenseId, reason: "产线删除", adminToken: "test-token" });
const orphanModelDir = path.join(dataDirectory, "models", line.productionLine.deviceId);
const orphanFileDir = path.join(dataDirectory, "files", "ReinLoop_GUI", line.productionLine.deviceId);
await fs.promises.mkdir(orphanModelDir, { recursive: true });
await fs.promises.mkdir(orphanFileDir, { recursive: true });
await fs.promises.writeFile(path.join(orphanModelDir, "orphan.bin"), "orphan-model");
await fs.promises.writeFile(path.join(orphanFileDir, "orphan.json"), "orphan-file");
const deleted = await post({
type: "deleteProductionLine",
companyId: company.company.id,
productionLineId: line.productionLine.id,
adminToken: "test-token"
});
assert.equal(deleted.success, true);
assert.ok(deleted.deletedFiles >= 1);
assert.ok(deleted.deletedLicenses >= 1);
assert.ok(deleted.deletedDirectories >= 1);
await assert.rejects(fs.promises.access(orphanModelDir));
await assert.rejects(fs.promises.access(orphanFileDir));
const organizations = await post({ type: "listOrganizations", adminToken: "test-token" });
const listedCompany = organizations.companies.find((item) => item.id === company.company.id);
assert.ok(listedCompany);
assert.equal(listedCompany.productionLines.some((item) => item.id === line.productionLine.id), false);
const models = await post({ type: "listModels", folder: `${line.productionLine.deviceId}/model_config` });
assert.deepEqual(models.fileList, []);
});
test("deleteCompany cascades revoked data cleanup", async () => {
const suffix = crypto.randomUUID().slice(0, 8);
const company = await post({
type: "createCompany", name: `删除公司-${suffix}`, code: `del-co-${suffix}`,
adminToken: "test-token"
});
const line = await post({
type: "createProductionLine", companyId: company.company.id,
name: "子产线", code: "line-1", adminToken: "test-token"
});
const modelIssued = await post({
type: "issueModelUpload", deviceId: line.productionLine.deviceId,
fileName: "company-cascade.bin", adminToken: "test-token"
});
const modelForm = new FormData();
modelForm.append("file", new Blob(["cascade-model"]), "company-cascade.bin");
assert.equal((await fetch(modelIssued.uploadMetadata.url, { method: "POST", body: modelForm })).status, 204);
const licenseId = crypto.randomUUID();
const payload = {
license_id: licenseId,
company_id: company.company.id,
production_line_id: line.productionLine.id,
customer: company.company.name,
device_id: line.productionLine.deviceId,
issued: "2026-08-01 10:00",
expiry: "2028-08-01 10:00",
features: "*"
};
await post({
type: "createLicense", licenseId,
companyId: company.company.id,
productionLineId: line.productionLine.id,
customer: payload.customer,
issued: payload.issued,
expiry: payload.expiry,
features: payload.features,
license: signLicense(payload),
adminToken: "test-token"
});
await post({ type: "revokeLicense", licenseId, reason: "公司删除", adminToken: "test-token" });
const deleted = await post({ type: "deleteCompany", companyId: company.company.id, adminToken: "test-token" });
assert.equal(deleted.success, true);
assert.equal(deleted.deletedCompanyId, company.company.id);
assert.ok(deleted.deletedProductionLines >= 1);
assert.ok(deleted.deletedLicenses >= 1);
assert.ok(deleted.deletedFiles >= 1);
const organizations = await post({ type: "listOrganizations", adminToken: "test-token" });
assert.equal(organizations.companies.some((item) => item.id === company.company.id), false);
const models = await post({ type: "listModels", folder: `${line.productionLine.deviceId}/model_config` });
assert.deepEqual(models.fileList, []);
});
test("deleteCompany blocks when active licenses exist", async () => {
const suffix = crypto.randomUUID().slice(0, 8);
const company = await post({
type: "createCompany", name: `删除公司阻断-${suffix}`, code: `del-co-block-${suffix}`,
adminToken: "test-token"
});
const line = await post({
type: "createProductionLine", companyId: company.company.id,
name: "阻断产线", code: "line-1", adminToken: "test-token"
});
const licenseId = crypto.randomUUID();
const payload = {
license_id: licenseId,
company_id: company.company.id,
production_line_id: line.productionLine.id,
customer: company.company.name,
device_id: line.productionLine.deviceId,
issued: "2026-08-01 10:00",
expiry: "2028-08-01 10:00",
features: "*"
};
await post({
type: "createLicense", licenseId,
companyId: company.company.id,
productionLineId: line.productionLine.id,
customer: payload.customer,
issued: payload.issued,
expiry: payload.expiry,
features: payload.features,
license: signLicense(payload),
adminToken: "test-token"
});
const blocked = await post({ type: "deleteCompany", companyId: company.company.id, adminToken: "test-token" });
assert.equal(blocked.success, false);
assert.equal(blocked.errCode, "ACTIVE_LICENSES_PRESENT");
});