mirror of
https://github.com/sysadminsmedia/homebox.git
synced 2026-01-01 18:47:20 +01:00
Compare commits
3 Commits
copilot/au
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abad8ad1ee | ||
|
|
b02b39c1b3 | ||
|
|
b290175bb0 |
@@ -21,6 +21,9 @@
|
|||||||
<img src="https://img.shields.io/mastodon/follow/110749314839831923?domain=infosec.exchange"/>
|
<img src="https://img.shields.io/mastodon/follow/110749314839831923?domain=infosec.exchange"/>
|
||||||
<img src="https://img.shields.io/lemmy/homebox%40lemmy.world?label=lemmy"/>
|
<img src="https://img.shields.io/lemmy/homebox%40lemmy.world?label=lemmy"/>
|
||||||
</p>
|
</p>
|
||||||
|
<p align="center" style="width: 100%;">
|
||||||
|
<a href="https://www.pikapods.com/pods?run=homebox"><img src="https://www.pikapods.com/static/run-button.svg"/></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
## What is HomeBox
|
## What is HomeBox
|
||||||
|
|
||||||
|
|||||||
@@ -1,129 +1,182 @@
|
|||||||
|
import type { Page } from "@playwright/test";
|
||||||
import { expect, test } from "@playwright/test";
|
import { expect, test } from "@playwright/test";
|
||||||
|
|
||||||
test.describe("Wipe Inventory E2E Test", () => {
|
const STATUS_ROUTE = "**/api/v1/status";
|
||||||
test.beforeEach(async ({ page }) => {
|
const WIPE_ROUTE = "**/api/v1/actions/wipe-inventory";
|
||||||
// Login as demo user (owner with permissions)
|
|
||||||
await page.goto("/");
|
const buildStatusResponse = (demo: boolean) => ({
|
||||||
await page.fill("input[type='text']", "demo@example.com");
|
allowRegistration: true,
|
||||||
await page.fill("input[type='password']", "demo");
|
build: { buildTime: new Date().toISOString(), commit: "test", version: "v0.0.0" },
|
||||||
await page.click("button[type='submit']");
|
demo,
|
||||||
await expect(page).toHaveURL("/home");
|
health: true,
|
||||||
|
labelPrinting: false,
|
||||||
|
latest: { date: new Date().toISOString(), version: "v0.0.0" },
|
||||||
|
message: "",
|
||||||
|
oidc: { allowLocal: true, autoRedirect: false, buttonText: "", enabled: false },
|
||||||
|
title: "Homebox",
|
||||||
|
versions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
async function mockStatus(page: Page, demo: boolean) {
|
||||||
|
await page.route(STATUS_ROUTE, route => {
|
||||||
|
route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: "application/json",
|
||||||
|
body: JSON.stringify(buildStatusResponse(demo)),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function login(page: Page, email = "demo@example.com", password = "demo") {
|
||||||
|
await page.goto("/home");
|
||||||
|
await expect(page).toHaveURL("/");
|
||||||
|
await page.fill("input[type='text']", email);
|
||||||
|
await page.fill("input[type='password']", password);
|
||||||
|
await page.click("button[type='submit']");
|
||||||
|
await expect(page).toHaveURL("/home");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openWipeInventory(page: Page) {
|
||||||
|
await page.goto("/tools");
|
||||||
|
await page.waitForLoadState("networkidle");
|
||||||
|
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
||||||
|
|
||||||
|
const wipeButton = page.getByRole("button", { name: "Wipe Inventory" }).last();
|
||||||
|
await expect(wipeButton).toBeVisible();
|
||||||
|
await wipeButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
test.describe("Wipe Inventory", () => {
|
||||||
|
test("shows demo mode warning without wipe options", async ({ page }) => {
|
||||||
|
await mockStatus(page, true);
|
||||||
|
await login(page);
|
||||||
|
await openWipeInventory(page);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByText(
|
||||||
|
"Inventory, labels, locations and maintenance records cannot be wiped whilst Homebox is in demo mode.",
|
||||||
|
{ exact: false }
|
||||||
|
)
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
await expect(page.locator("input#wipe-labels-checkbox")).toHaveCount(0);
|
||||||
|
await expect(page.locator("input#wipe-locations-checkbox")).toHaveCount(0);
|
||||||
|
await expect(page.locator("input#wipe-maintenance-checkbox")).toHaveCount(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should open wipe inventory dialog with all options", async ({ page }) => {
|
test.describe("production mode", () => {
|
||||||
// Navigate to Tools page
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto("/tools");
|
await mockStatus(page, false);
|
||||||
await page.waitForLoadState("networkidle");
|
await login(page);
|
||||||
|
|
||||||
// Scroll to the bottom where wipe inventory is located
|
|
||||||
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
|
||||||
await page.waitForTimeout(500);
|
|
||||||
|
|
||||||
// Find and click the Wipe Inventory button
|
|
||||||
const wipeButton = page.locator("button", { hasText: "Wipe Inventory" }).last();
|
|
||||||
await expect(wipeButton).toBeVisible();
|
|
||||||
await wipeButton.click();
|
|
||||||
|
|
||||||
// Wait for dialog to appear
|
|
||||||
await page.waitForTimeout(1000);
|
|
||||||
|
|
||||||
// Verify dialog title is visible
|
|
||||||
await expect(page.locator("text=Wipe Inventory").first()).toBeVisible();
|
|
||||||
|
|
||||||
// Verify all checkboxes are present
|
|
||||||
await expect(page.locator("input#wipe-labels-checkbox")).toBeVisible();
|
|
||||||
await expect(page.locator("input#wipe-locations-checkbox")).toBeVisible();
|
|
||||||
await expect(page.locator("input#wipe-maintenance-checkbox")).toBeVisible();
|
|
||||||
|
|
||||||
// Verify labels for checkboxes
|
|
||||||
await expect(page.locator("label[for='wipe-labels-checkbox']")).toBeVisible();
|
|
||||||
await expect(page.locator("label[for='wipe-locations-checkbox']")).toBeVisible();
|
|
||||||
await expect(page.locator("label[for='wipe-maintenance-checkbox']")).toBeVisible();
|
|
||||||
|
|
||||||
// Verify both Cancel and Confirm buttons are present
|
|
||||||
await expect(page.locator("button", { hasText: "Cancel" })).toBeVisible();
|
|
||||||
const confirmButton = page.locator("button", { hasText: "Confirm" });
|
|
||||||
await expect(confirmButton).toBeVisible();
|
|
||||||
|
|
||||||
// Take screenshot of the modal
|
|
||||||
await page.screenshot({
|
|
||||||
path: "/tmp/playwright-logs/wipe-inventory-modal-initial.png",
|
|
||||||
});
|
});
|
||||||
console.log("✅ Screenshot saved: wipe-inventory-modal-initial.png");
|
|
||||||
|
|
||||||
// Check all three options
|
test("renders wipe options and submits all flags", async ({ page }) => {
|
||||||
await page.check("input#wipe-labels-checkbox");
|
await page.route(WIPE_ROUTE, route => {
|
||||||
await page.check("input#wipe-locations-checkbox");
|
route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ completed: 0 }) });
|
||||||
await page.check("input#wipe-maintenance-checkbox");
|
});
|
||||||
await page.waitForTimeout(500);
|
|
||||||
|
|
||||||
// Verify checkboxes are checked
|
await openWipeInventory(page);
|
||||||
await expect(page.locator("input#wipe-labels-checkbox")).toBeChecked();
|
await expect(page.getByText("Wipe Inventory").first()).toBeVisible();
|
||||||
await expect(page.locator("input#wipe-locations-checkbox")).toBeChecked();
|
|
||||||
await expect(page.locator("input#wipe-maintenance-checkbox")).toBeChecked();
|
|
||||||
|
|
||||||
// Take screenshot with all options checked
|
const labels = page.locator("input#wipe-labels-checkbox");
|
||||||
await page.screenshot({
|
const locations = page.locator("input#wipe-locations-checkbox");
|
||||||
path: "/tmp/playwright-logs/wipe-inventory-modal-options-checked.png",
|
const maintenance = page.locator("input#wipe-maintenance-checkbox");
|
||||||
|
|
||||||
|
await expect(labels).toBeVisible();
|
||||||
|
await expect(locations).toBeVisible();
|
||||||
|
await expect(maintenance).toBeVisible();
|
||||||
|
|
||||||
|
await labels.check();
|
||||||
|
await locations.check();
|
||||||
|
await maintenance.check();
|
||||||
|
|
||||||
|
const requestPromise = page.waitForRequest(WIPE_ROUTE);
|
||||||
|
await page.getByRole("button", { name: "Confirm" }).last().click();
|
||||||
|
const request = await requestPromise;
|
||||||
|
|
||||||
|
expect(request.postDataJSON()).toEqual({
|
||||||
|
wipeLabels: true,
|
||||||
|
wipeLocations: true,
|
||||||
|
wipeMaintenance: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(page.locator("[role='status']").first()).toBeVisible();
|
||||||
});
|
});
|
||||||
console.log("✅ Screenshot saved: wipe-inventory-modal-options-checked.png");
|
|
||||||
|
|
||||||
// Click Confirm button
|
test("blocks wipe attempts from non-owners", async ({ page }) => {
|
||||||
await confirmButton.click();
|
await page.route(WIPE_ROUTE, route => {
|
||||||
await page.waitForTimeout(2000);
|
route.fulfill({
|
||||||
|
status: 403,
|
||||||
|
contentType: "application/json",
|
||||||
|
body: JSON.stringify({ message: "forbidden" }),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Wait for the dialog to close (verify button is no longer visible)
|
await openWipeInventory(page);
|
||||||
await expect(confirmButton).not.toBeVisible({ timeout: 5000 });
|
|
||||||
|
|
||||||
// Check for success toast notification
|
const requestPromise = page.waitForRequest(WIPE_ROUTE);
|
||||||
// The toast should contain text about items being deleted
|
await page.getByRole("button", { name: "Confirm" }).last().click();
|
||||||
const toastLocator = page.locator("[role='status'], [class*='toast'], [class*='sonner']");
|
await requestPromise;
|
||||||
await expect(toastLocator.first()).toBeVisible({ timeout: 10000 });
|
|
||||||
|
|
||||||
// Take screenshot of the page after confirmation
|
await expect(page.getByText("Failed to wipe inventory.")).toBeVisible();
|
||||||
await page.screenshot({
|
|
||||||
path: "/tmp/playwright-logs/after-wipe-confirmation.png",
|
|
||||||
fullPage: true,
|
|
||||||
});
|
});
|
||||||
console.log("✅ Screenshot saved: after-wipe-confirmation.png");
|
|
||||||
|
|
||||||
console.log("✅ Test completed successfully!");
|
const checkboxCases = [
|
||||||
console.log("✅ Wipe Inventory dialog opened correctly");
|
{
|
||||||
console.log("✅ All three options (labels, locations, maintenance) are available");
|
name: "labels only",
|
||||||
console.log("✅ Confirm button triggers the action");
|
selection: { labels: true, locations: false, maintenance: false },
|
||||||
console.log("✅ Dialog closes after confirmation");
|
},
|
||||||
});
|
{
|
||||||
|
name: "locations only",
|
||||||
|
selection: { labels: false, locations: true, maintenance: false },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "maintenance only",
|
||||||
|
selection: { labels: false, locations: false, maintenance: true },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
test("should cancel wipe inventory operation", async ({ page }) => {
|
for (const scenario of checkboxCases) {
|
||||||
// Navigate to Tools page
|
test(`submits correct flags when ${scenario.name} is selected`, async ({ page }) => {
|
||||||
await page.goto("/tools");
|
await page.route(WIPE_ROUTE, route => {
|
||||||
await page.waitForLoadState("networkidle");
|
route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ completed: 0 }) });
|
||||||
|
});
|
||||||
|
|
||||||
// Scroll to wipe inventory section
|
await openWipeInventory(page);
|
||||||
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
await expect(page.getByText("Wipe Inventory").first()).toBeVisible();
|
||||||
await page.waitForTimeout(500);
|
|
||||||
|
|
||||||
// Click Wipe Inventory button
|
const labels = page.locator("input#wipe-labels-checkbox");
|
||||||
const wipeButton = page.locator("button", { hasText: "Wipe Inventory" }).last();
|
const locations = page.locator("input#wipe-locations-checkbox");
|
||||||
await wipeButton.click();
|
const maintenance = page.locator("input#wipe-maintenance-checkbox");
|
||||||
await page.waitForTimeout(1000);
|
|
||||||
|
|
||||||
// Verify dialog is open
|
if (scenario.selection.labels) {
|
||||||
await expect(page.locator("text=Wipe Inventory").first()).toBeVisible();
|
await labels.check();
|
||||||
|
} else {
|
||||||
|
await labels.uncheck();
|
||||||
|
}
|
||||||
|
|
||||||
// Click Cancel button
|
if (scenario.selection.locations) {
|
||||||
const cancelButton = page.locator("button", { hasText: "Cancel" });
|
await locations.check();
|
||||||
await cancelButton.click();
|
} else {
|
||||||
await page.waitForTimeout(1000);
|
await locations.uncheck();
|
||||||
|
}
|
||||||
|
|
||||||
// Verify dialog is closed
|
if (scenario.selection.maintenance) {
|
||||||
await expect(page.locator("text=Wipe Inventory").first()).not.toBeVisible({ timeout: 5000 });
|
await maintenance.check();
|
||||||
|
} else {
|
||||||
|
await maintenance.uncheck();
|
||||||
|
}
|
||||||
|
|
||||||
// Take screenshot after cancel
|
const requestPromise = page.waitForRequest(WIPE_ROUTE);
|
||||||
await page.screenshot({
|
await page.getByRole("button", { name: "Confirm" }).last().click();
|
||||||
path: "/tmp/playwright-logs/after-cancel.png",
|
const request = await requestPromise;
|
||||||
});
|
|
||||||
console.log("✅ Screenshot saved: after-cancel.png");
|
expect(request.postDataJSON()).toEqual({
|
||||||
console.log("✅ Cancel button works correctly");
|
wipeLabels: scenario.selection.labels,
|
||||||
|
wipeLocations: scenario.selection.locations,
|
||||||
|
wipeMaintenance: scenario.selection.maintenance,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user