Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions frontend/src/pages/admin/AdminImpersonationSafeguards.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { describe, it, expect, beforeEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { http, HttpResponse } from "msw";
import { server } from "../../test/mocks/server";
import { MemoryRouter } from "react-router-dom";
import AdminImpersonationSafeguards from "./AdminImpersonationSafeguards";

const mockSession = {
id: "session-1",
adminId: "admin-security-chief",
impersonatedUserId: "user-target-88",
reason: "Investigating user issue ticket #SUP-4412",
approvalTicketId: "SUP-4412",
status: "ACTIVE" as const,
tokenHash: "hash-123",
maxDurationMinutes: 30,
expiresAt: new Date(Date.now() + 1800000).toISOString(),
endedAt: null,
ipAddress: "127.0.0.1",
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};

const mockAuditLogs = [
{
id: "log-1",
sessionId: "session-1",
requestMethod: "GET",
requestPath: "/api/v1/user/profile",
actorAdminId: "admin-security-chief",
targetUserId: "user-target-88",
ipAddress: "127.0.0.1",
timestamp: new Date().toISOString(),
},
];

let currentSessions = [mockSession];

function setup() {
currentSessions = [mockSession];
server.use(
http.get("/api/v1/admin/impersonation/sessions", () =>
HttpResponse.json({ sessions: currentSessions })
),
http.post("/api/v1/admin/impersonation/start", () =>
HttpResponse.json({
session: mockSession,
token: "impersonation-token-123",
})
),
http.post("/api/v1/admin/impersonation/stop", () => {
currentSessions = [{ ...mockSession, status: "ENDED" }];
return HttpResponse.json({ success: true });
}),
http.get("/api/v1/admin/impersonation/audit-logs", () =>
HttpResponse.json({ auditLogs: mockAuditLogs })
)
);
window.localStorage.setItem("bridge-watch:admin-api-key:v1", JSON.stringify("test-token"));
}

function renderPage() {
return render(
<MemoryRouter>
<AdminImpersonationSafeguards />
</MemoryRouter>
);
}

describe("AdminImpersonationSafeguards page", () => {
beforeEach(() => {
setup();
});

it("renders heading and active impersonation session", async () => {
renderPage();
expect(
screen.getByRole("heading", { name: /admin impersonation safeguards/i })
).toBeInTheDocument();

await waitFor(() => {
expect(screen.getByText(/ACTIVE IMPERSONATION SESSION/i)).toBeInTheDocument();
});
expect(screen.getAllByText("user-target-88").length).toBeGreaterThan(0);
});

it("handles stopping an impersonation session", async () => {
renderPage();

const stopButton = await screen.findByRole("button", { name: /end session now/i });
expect(stopButton).toBeInTheDocument();
await userEvent.click(stopButton);

await waitFor(() => {
expect(screen.queryByText(/ACTIVE IMPERSONATION SESSION/i)).not.toBeInTheDocument();
});
});

it("fetches audit logs when session detail is requested", async () => {
renderPage();

await waitFor(() => {
expect(screen.getAllByText("user-target-88").length).toBeGreaterThan(0);
});

const auditTrailButtons = screen.getAllByRole("button", { name: /audit trail/i });
if (auditTrailButtons.length > 0) {
await userEvent.click(auditTrailButtons[0]);
await waitFor(() => {
expect(screen.getByText("/api/v1/user/profile")).toBeInTheDocument();
});
}
});
});
103 changes: 103 additions & 0 deletions frontend/src/pages/admin/AllowlistManagement.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { describe, it, expect, beforeEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { http, HttpResponse } from "msw";
import { server } from "../../test/mocks/server";
import { MemoryRouter } from "react-router-dom";
import AllowlistManagement from "./AllowlistManagement";

const mockAllowlist = [
{
id: "entry-1",
contractAddress: "CCW67TSB3SSS4ZXGBOI2CCBGH2QPBDH2VMKCEW47GFZ2G5J57TL2JG3V",
addedBy: "admin-1",
addedAt: new Date().toISOString(),
isActive: true,
},
];

const mockRequests = [
{
id: "req-1",
contractAddress: "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMWAXA26TXTXFX",
action: "add" as const,
reason: "New liquidity pool integration",
requestedBy: "op-lead",
status: "pending" as const,
reviewedBy: null,
reviewComment: null,
reviewedAt: null,
createdAt: new Date().toISOString(),
},
];

function setup() {
server.use(
http.get("/api/v1/admin/allowlist", () =>
HttpResponse.json({ allowlist: mockAllowlist })
),
http.get("/api/v1/admin/allowlist/change-requests", () =>
HttpResponse.json({ changeRequests: mockRequests })
),
http.post("/api/v1/admin/allowlist/change-requests", () =>
HttpResponse.json({ success: true }, { status: 201 })
),
http.post("/api/v1/admin/allowlist/change-requests/:id/review", () =>
HttpResponse.json({ success: true })
)
);
}

function renderPage() {
return render(
<MemoryRouter>
<AllowlistManagement />
</MemoryRouter>
);
}

describe("AllowlistManagement page", () => {
beforeEach(() => {
setup();
});

it("renders page heading and allowlist entries", async () => {
renderPage();
expect(
screen.getByRole("heading", { name: /contract allowlist management/i })
).toBeInTheDocument();

await waitFor(() => {
expect(
screen.getByText("CCW67TSB3SSS4ZXGBOI2CCBGH2QPBDH2VMKCEW47GFZ2G5J57TL2JG3V")
).toBeInTheDocument();
});
});

it("switches tabs and displays change requests", async () => {
renderPage();
const requestsTabButton = screen.getByRole("button", { name: /change requests/i });
await userEvent.click(requestsTabButton);

await waitFor(() => {
expect(
screen.getByText("CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMWAXA26TXTXFX")
).toBeInTheDocument();
});
});

it("allows submitting a new change request", async () => {
const { container } = renderPage();
const addressInput = screen.getByPlaceholderText("0x...");
const textareaReason = container.querySelector("textarea")!;
const submitBtn = screen.getByRole("button", { name: /submit request/i });

await userEvent.type(addressInput, "CABC1234567890TESTCONTRACTADDRESSEXAMPLE");
await userEvent.type(textareaReason, "Testing submission flow");
await userEvent.click(submitBtn);

await waitFor(() => {
expect(addressInput).toHaveValue("");
});
});
});
119 changes: 119 additions & 0 deletions frontend/src/pages/admin/ParseQuarantineQueue.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { describe, it, expect, beforeEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { http, HttpResponse } from "msw";
import { server } from "../../test/mocks/server";
import { MemoryRouter } from "react-router-dom";
import ParseQuarantineQueue from "./ParseQuarantineQueue";

const mockRecord = {
id: "qr-101",
source: "horizon-ingestor",
dataType: "transaction_meta",
rawPayload: { hash: "0x123abc" },
parseError: "Unexpected end of JSON input",
errorCode: "ERR_JSON_PARSE",
status: "quarantined" as const,
retryCount: 1,
priority: 5,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};

const mockStats = {
total: 10,
byStatus: {
quarantined: 4,
in_review: 2,
resolved: 3,
disposed: 1,
failed: 0,
},
bySource: {
"horizon-ingestor": 10,
},
};

function setup() {
server.use(
http.get("/api/v1/admin/quarantine", () =>
HttpResponse.json({ records: [mockRecord] })
),
http.get("/api/v1/admin/quarantine/stats", () =>
HttpResponse.json({ stats: mockStats })
),
http.post("/api/v1/admin/quarantine", () =>
HttpResponse.json({ record: mockRecord }, { status: 201 })
),
http.post("/api/v1/admin/quarantine/:id/retry", () =>
HttpResponse.json({ record: { ...mockRecord, status: "resolved" } })
),
http.post("/api/v1/admin/quarantine/:id/resolve", () =>
HttpResponse.json({ record: { ...mockRecord, status: "resolved" } })
),
http.delete("/api/v1/admin/quarantine/:id", () =>
HttpResponse.json({ success: true })
)
);
window.localStorage.setItem("bridge-watch:admin-api-key:v1", JSON.stringify("test-token"));
}

function renderPage() {
return render(
<MemoryRouter>
<ParseQuarantineQueue />
</MemoryRouter>
);
}

describe("ParseQuarantineQueue page", () => {
beforeEach(() => {
setup();
});

it("renders heading and quarantine queue items", async () => {
renderPage();
expect(
screen.getByRole("heading", { name: /failed parse quarantine/i })
).toBeInTheDocument();

await waitFor(() => {
expect(screen.getByText("horizon-ingestor")).toBeInTheDocument();
});
expect(screen.getByText(/ERR_JSON_PARSE/i)).toBeInTheDocument();
});

it("handles enqueueing a new quarantine item", async () => {
renderPage();

const sourceInput = screen.getByPlaceholderText("some-exchange");
const dataTypeInput = screen.getByPlaceholderText("asset");
const parseErrorInput = screen.getByPlaceholderText("Unexpected field 'foo'");

await userEvent.type(sourceInput, "horizon-stream");
await userEvent.type(dataTypeInput, "ledger_header");
await userEvent.type(parseErrorInput, "SyntaxError");

const enqueueBtn = screen.getByRole("button", { name: /enqueue record/i });
await userEvent.click(enqueueBtn);

await waitFor(() => {
expect(sourceInput).toHaveValue("");
});
});

it("retries and resolves quarantine items", async () => {
renderPage();

await waitFor(() => {
expect(screen.getByText("horizon-ingestor")).toBeInTheDocument();
});

const retryBtn = screen.getByRole("button", { name: /retry/i });
await userEvent.click(retryBtn);

await waitFor(() => {
expect(screen.getByRole("heading", { name: /failed parse quarantine/i })).toBeInTheDocument();
});
});
});
56 changes: 56 additions & 0 deletions frontend/src/pages/admin/SecurityEventCorrelation.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { describe, it, expect } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { MemoryRouter } from "react-router-dom";
import SecurityEventCorrelation from "./SecurityEventCorrelation";

function renderPage() {
return render(
<MemoryRouter>
<SecurityEventCorrelation />
</MemoryRouter>
);
}

describe("SecurityEventCorrelation page", () => {
it("renders page heading and initial security correlations", () => {
renderPage();
expect(
screen.getByRole("heading", { name: /security event correlation/i })
).toBeInTheDocument();

expect(
screen.getByText("Multiple Failed Webhook & Auth Signatures")
).toBeInTheDocument();
expect(
screen.getByText("Sensitive Field Bulk Export Anomaly")
).toBeInTheDocument();
});

it("filters correlations by search term", async () => {
renderPage();
const searchInput = screen.getByPlaceholderText(/search correlation views/i);
await userEvent.type(searchInput, "Webhook");

expect(
screen.getByText("Multiple Failed Webhook & Auth Signatures")
).toBeInTheDocument();
expect(
screen.queryByText("Sensitive Field Bulk Export Anomaly")
).not.toBeInTheDocument();
});

it("opens create modal and adds a correlation rule", async () => {
const { container } = renderPage();
const createBtn = screen.getByRole("button", { name: /\+ Create Correlation View/i });
await userEvent.click(createBtn);

const titleInput = container.querySelector('form input[type="text"]')!;
await userEvent.type(titleInput, "Unusual Geo IP Spike");

const submitBtn = screen.getByRole("button", { name: /save correlation view/i });
await userEvent.click(submitBtn);

expect(screen.getByText("Unusual Geo IP Spike")).toBeInTheDocument();
});
});
Loading
Loading