251 lines
10 KiB
JavaScript
251 lines
10 KiB
JavaScript
const assert = require("assert");
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const ejs = require("ejs");
|
|
|
|
const root = path.join(__dirname, "..");
|
|
const sandbox = fs.mkdtempSync(path.join(root, ".tmp-lumi-feedback-test-"));
|
|
const serviceDir = path.join(sandbox, "src", "services");
|
|
|
|
fs.mkdirSync(serviceDir, { recursive: true });
|
|
for (const file of ["db.js", "feedback.js"]) {
|
|
fs.copyFileSync(path.join(root, "src", "services", file), path.join(serviceDir, file));
|
|
}
|
|
|
|
let database = null;
|
|
try {
|
|
database = require(path.join(serviceDir, "db.js"));
|
|
database.migrate();
|
|
const feedback = require(path.join(serviceDir, "feedback.js"));
|
|
const db = database.db;
|
|
const now = Date.now();
|
|
db.prepare(
|
|
"INSERT INTO user_profiles (id, internal_username, created_at, updated_at) VALUES (?, ?, ?, ?)"
|
|
).run("user-1", "FeedbackUser", now, now);
|
|
db.prepare(
|
|
"INSERT INTO user_profiles (id, internal_username, created_at, updated_at) VALUES (?, ?, ?, ?)"
|
|
).run("admin-1", "AdminUser", now, now);
|
|
db.prepare(
|
|
"INSERT INTO user_profiles (id, internal_username, created_at, updated_at) VALUES (?, ?, ?, ?)"
|
|
).run("user-2", "SecondUser", now, now);
|
|
|
|
const entry = feedback.createFeedback({
|
|
summary: "Save button fails",
|
|
category: "broken_interaction",
|
|
severity: "broken",
|
|
scope_type: "element",
|
|
scope_label: "Save settings button",
|
|
description: "Clicking the save button does not apply the changes.",
|
|
steps_to_reproduce: "Open settings, change a field, click save.",
|
|
expected_behavior: "The settings should be saved.",
|
|
actual_behavior: "The page reloads without saving.",
|
|
current_url: "http://localhost/admin/settings",
|
|
page_title: "Settings",
|
|
target_metadata: {
|
|
selector: "#save-settings",
|
|
tag: "button",
|
|
text: "Save settings",
|
|
secret: "must not persist"
|
|
},
|
|
diagnostics: {
|
|
user_agent: "verification-agent",
|
|
viewport: "1200x800",
|
|
screenshot_mode: "target",
|
|
hidden: "must not persist"
|
|
}
|
|
}, { id: "user-1" }, {
|
|
screenshot: {
|
|
path: "feedback/screenshots/test-shot.png",
|
|
mime: "image/png",
|
|
size: 2048
|
|
}
|
|
});
|
|
|
|
assert.equal(entry.status, "new");
|
|
assert.equal(entry.scope_label, "Clicked element: Save settings");
|
|
assert.equal(entry.target_metadata.secret, undefined);
|
|
assert.equal(entry.diagnostics.hidden, undefined);
|
|
assert.equal(entry.screenshot.mime, "image/png");
|
|
assert.equal(feedback.listPublicFeedback({ userId: "user-1" })[0].is_mine, true);
|
|
assert.equal(feedback.listMyFeedback("user-1").length, 1);
|
|
assert.equal(feedback.supportFeedback(entry.id, { id: "user-2" }), 1);
|
|
assert.equal(feedback.listPublicFeedback({ userId: "user-2" })[0].supported_by_me, true);
|
|
|
|
feedback.addSubmitterComment(entry.id, "This also happens after a hard reload.", { id: "user-1" });
|
|
feedback.addSubmitterComment(entry.id, "I see this too on the same settings page.", { id: "user-2" });
|
|
const withComment = feedback.getFeedbackForSubmitter(entry.id, "user-1");
|
|
assert.equal(withComment.comments.length, 2);
|
|
assert.equal(withComment.comments[0].kind, "submitter_comment");
|
|
assert.equal(withComment.comments[1].kind, "public_comment");
|
|
const publicViewerEntry = feedback.getFeedbackForViewer(entry.id, "user-2");
|
|
assert.equal(publicViewerEntry.is_mine, false);
|
|
assert.equal(publicViewerEntry.screenshot, null);
|
|
assert(publicViewerEntry.comments.some((comment) => comment.kind === "public_comment"));
|
|
|
|
feedback.markFeedbackViewed("user-1");
|
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 2);
|
|
feedback.adminUpdateFeedback(entry.id, {
|
|
status: "needs_more_context",
|
|
category: "bug",
|
|
severity: "urgent",
|
|
admin_reply: "Can you add which browser this happened in?",
|
|
work_note: "Likely settings form regression.",
|
|
status_note: "Need browser detail."
|
|
}, { id: "admin-1" });
|
|
const notices = feedback.notificationSummary("user-1");
|
|
assert.equal(notices.needs_context, 1);
|
|
const adminEntry = feedback.getFeedbackForAdmin(entry.id);
|
|
assert(adminEntry.comments.some((comment) => comment.kind === "admin_reply"));
|
|
assert(adminEntry.comments.some((comment) => comment.kind === "work_note"));
|
|
const submitterEntry = feedback.getFeedbackForSubmitter(entry.id, "user-1");
|
|
assert.equal(submitterEntry.comments.some((comment) => comment.kind === "work_note"), false);
|
|
|
|
feedback.adminUpdateFeedback(entry.id, {
|
|
status: "closed",
|
|
category: "bug",
|
|
severity: "urgent",
|
|
status_note: "Finalized after verification."
|
|
}, { id: "admin-1" });
|
|
assert.equal(feedback.getFeedbackForAdmin(entry.id).status, "closed");
|
|
feedback.adminUpdateFeedback(entry.id, {
|
|
status: "reviewed",
|
|
category: "bug",
|
|
severity: "urgent",
|
|
status_note: "Reopened after verification."
|
|
}, { id: "admin-1" });
|
|
assert.equal(feedback.getFeedbackForAdmin(entry.id).status, "reviewed");
|
|
assert(feedback.findSimilarFeedback({
|
|
summary: "Settings save button does nothing",
|
|
category: "bug",
|
|
scope_type: "element",
|
|
current_url: "http://localhost/admin/settings"
|
|
}, { userId: "user-2" }).some((match) => match.id === entry.id));
|
|
assert(feedback.findSimilarFeedback({
|
|
summary: "Controls are weird",
|
|
description: "Clicking the save button does not apply changes after editing settings.",
|
|
category: "bug",
|
|
scope_type: "element",
|
|
current_url: "http://localhost/admin/settings"
|
|
}, { userId: "user-2" }).some((match) => match.id === entry.id));
|
|
|
|
const cleanupEntry = feedback.createFeedback({
|
|
summary: "Sensitive screenshot cleanup",
|
|
category: "bug",
|
|
severity: "minor",
|
|
scope_type: "page",
|
|
description: "This report exists to verify cleanup behavior.",
|
|
current_url: "http://localhost/admin/feedback",
|
|
page_title: "Feedback review",
|
|
diagnostics: { user_agent: "sensitive-agent" }
|
|
}, { id: "user-1" }, {
|
|
screenshot: {
|
|
path: "feedback/screenshots/cleanup-shot.png",
|
|
mime: "image/png",
|
|
size: 1024
|
|
},
|
|
attachments: [{
|
|
path: "feedback/attachments/cleanup-note.txt",
|
|
mime: "text/plain",
|
|
size: 128,
|
|
original_name: "cleanup-note.txt"
|
|
}]
|
|
});
|
|
assert.equal(feedback.getFeedbackForAdmin(cleanupEntry.id).attachments.length, 1);
|
|
let cleanedScreenshot = "";
|
|
let cleanedAttachment = "";
|
|
feedback.cleanupFeedback(cleanupEntry.id, {
|
|
clear_screenshot: "1",
|
|
clear_attachments: "1",
|
|
clear_diagnostics: "1",
|
|
clear_target_metadata: "1"
|
|
}, { id: "admin-1" }, {
|
|
deleteScreenshot(relativePath) {
|
|
cleanedScreenshot = relativePath;
|
|
},
|
|
deleteAttachment(relativePath) {
|
|
cleanedAttachment = relativePath;
|
|
}
|
|
});
|
|
const cleaned = feedback.getFeedbackForAdmin(cleanupEntry.id);
|
|
assert.equal(cleaned.screenshot, null);
|
|
assert.deepEqual(cleaned.diagnostics, {});
|
|
assert.deepEqual(cleaned.target_metadata, {});
|
|
assert.equal(cleaned.attachments.length, 0);
|
|
assert.equal(cleanedScreenshot, "feedback/screenshots/cleanup-shot.png");
|
|
assert.equal(cleanedAttachment, "feedback/attachments/cleanup-note.txt");
|
|
|
|
const feedbackView = path.join(root, "src", "web", "views", "feedback.ejs");
|
|
const adminView = path.join(root, "src", "web", "views", "admin-feedback.ejs");
|
|
const commonLocals = {
|
|
siteTitle: "Lumi Bot",
|
|
assetVersion: "verify",
|
|
theme: null,
|
|
botAvatar: null,
|
|
navSections: [],
|
|
user: { id: "user-1", username: "FeedbackUser" },
|
|
userAvatar: null,
|
|
userInitial: "F",
|
|
platformLogins: [],
|
|
platformLinks: [],
|
|
platforms: [],
|
|
flash: null,
|
|
softError: null,
|
|
feedbackNotifications: feedback.notificationSummary("user-1"),
|
|
feedbackOptions: feedback.feedbackOptions()
|
|
};
|
|
const userRendered = ejs.render(fs.readFileSync(feedbackView, "utf8"), {
|
|
...commonLocals,
|
|
title: "Feedback",
|
|
feedbackList: feedback.listPublicFeedback({ userId: "user-1" }),
|
|
myFeedback: feedback.listMyFeedback("user-1"),
|
|
selectedFeedback: feedback.getFeedbackForSubmitter(entry.id, "user-1")
|
|
}, { filename: feedbackView });
|
|
assert(userRendered.includes("My feedback"));
|
|
assert(userRendered.includes("Admin reply"));
|
|
assert(userRendered.includes("Open screenshot"));
|
|
assert(userRendered.includes("Community comment"));
|
|
const supporterRendered = ejs.render(fs.readFileSync(feedbackView, "utf8"), {
|
|
...commonLocals,
|
|
user: { id: "user-2", username: "SecondUser" },
|
|
title: "Feedback",
|
|
feedbackList: feedback.listPublicFeedback({ userId: "user-2" }),
|
|
myFeedback: feedback.listMyFeedback("user-2"),
|
|
selectedFeedback: null
|
|
}, { filename: feedbackView });
|
|
assert(supporterRendered.includes("Marked as affecting you too."));
|
|
|
|
const adminRendered = ejs.render(fs.readFileSync(adminView, "utf8"), {
|
|
...commonLocals,
|
|
user: { id: "admin-1", username: "AdminUser", isAdmin: true },
|
|
userInitial: "A",
|
|
title: "Feedback review",
|
|
feedbackItems: feedback.listFeedbackForAdmin({}),
|
|
filters: { status: "", category: "", severity: "", scope: "", area: "", submitter: "", date_from: "", date_to: "", needs_action: "", sort: "last_activity" }
|
|
}, { filename: adminView });
|
|
assert(adminRendered.includes("Feedback queue"));
|
|
assert(!adminRendered.includes("Convert useful reports"));
|
|
assert(adminRendered.includes("Finalize & Close"));
|
|
assert(adminRendered.includes("Delete"));
|
|
assert(adminRendered.includes("Open attached screenshot"));
|
|
assert(adminRendered.includes("Sensitive data cleanup"));
|
|
assert(adminRendered.includes("Remove attachments"));
|
|
assert(adminRendered.includes("Plugin/area"));
|
|
assert(adminRendered.includes("1 also affected"));
|
|
|
|
let deletedScreenshot = "";
|
|
feedback.deleteFeedback(entry.id, {
|
|
deleteScreenshot(relativePath) {
|
|
deletedScreenshot = relativePath;
|
|
}
|
|
});
|
|
assert.equal(feedback.getFeedbackForAdmin(entry.id), null);
|
|
assert.equal(db.prepare("SELECT COUNT(*) AS count FROM feedback_comments WHERE feedback_id = ?").get(entry.id).count, 0);
|
|
assert.equal(db.prepare("SELECT COUNT(*) AS count FROM feedback_status_history WHERE feedback_id = ?").get(entry.id).count, 0);
|
|
assert.equal(deletedScreenshot, "feedback/screenshots/test-shot.png");
|
|
|
|
console.log("Core feedback system verification passed.");
|
|
} finally {
|
|
database?.db.close();
|
|
fs.rmSync(sandbox, { recursive: true, force: true });
|
|
}
|