diff --git a/__tests__/api/noc.test.ts b/__tests__/api/noc.test.ts index 8b4f67d..4001e08 100644 --- a/__tests__/api/noc.test.ts +++ b/__tests__/api/noc.test.ts @@ -1,9 +1,19 @@ import { describe, expect, it } from "bun:test"; import api from "@/api"; +import { prisma } from "@/utils/db"; describe("NOC API Module", () => { const idDesa = "darmasaba"; + it("should return last sync timestamp", async () => { + const response = await api.handle( + new Request(`http://localhost/api/noc/last-sync?idDesa=${idDesa}`), + ); + expect(response.status).toBe(200); + const data = await response.json(); + expect(data).toHaveProperty("lastSyncedAt"); + }); + it("should return active divisions", async () => { const response = await api.handle( new Request(`http://localhost/api/noc/active-divisions?idDesa=${idDesa}`), @@ -71,4 +81,13 @@ describe("NOC API Module", () => { // Elysia returns 400 or 422 for validation errors expect([400, 422]).toContain(response.status); }); + + it("should return 401 for sync without admin auth", async () => { + const response = await api.handle( + new Request("http://localhost/api/noc/sync", { + method: "POST", + }), + ); + expect(response.status).toBe(401); + }); }); diff --git a/__tests__/e2e/noc-sync.spec.ts b/__tests__/e2e/noc-sync.spec.ts new file mode 100644 index 0000000..07efe22 --- /dev/null +++ b/__tests__/e2e/noc-sync.spec.ts @@ -0,0 +1,110 @@ +import { expect, test } from "@playwright/test"; + +test.describe("NOC Synchronization UI", () => { + test.beforeEach(async ({ page }) => { + // Mock the session API to simulate being logged in as an admin + await page.route("**/api/session", async (route) => { + await route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify({ + data: { + user: { + id: "user_123", + name: "Admin User", + email: "admin@example.com", + role: "admin", + }, + }, + }), + }); + }); + + // Mock the last-sync API + await page.route("**/api/noc/last-sync*", async (route) => { + await route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify({ + lastSyncedAt: new Date(Date.now() - 3600000).toISOString(), // 1 hour ago + }), + }); + }); + }); + + test("should navigate to NOC Sync page from sidebar", async ({ page }) => { + await page.goto("/"); + + // Open Settings/Pengaturan submenu if not open + const settingsNavLink = page.locator('button:has-text("Pengaturan")'); + await settingsNavLink.click(); + + // Click on Sinkronisasi NOC + const syncNavLink = page.locator('a:has-text("Sinkronisasi NOC")'); + // In Mantine NavLink with navigate, it might be a button or div with role button depending on implementation + // Based on Sidebar.tsx, it's a MantineNavLink which renders as a button or anchor + const syncLink = page.getByRole("button", { name: "Sinkronisasi NOC" }); + await syncLink.click(); + + // Verify we are on the sync page + await expect(page).toHaveURL(/\/pengaturan\/sinkronisasi/); + await expect(page.locator("h2")).toContainText("Sinkronisasi Data NOC"); + }); + + test("should perform synchronization successfully", async ({ page }) => { + await page.goto("/pengaturan/sinkronisasi"); + + // Initial state check + await expect(page.locator("text=Waktu Sinkronisasi Terakhir:")).toBeVisible(); + + // Mock the sync API + const now = new Date().toISOString(); + await page.route("**/api/noc/sync", async (route) => { + if (route.request().method() === "POST") { + await route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify({ + success: true, + message: "Sinkronisasi berhasil diselesaikan", + lastSyncedAt: now, + }), + }); + } + }); + + // Click Sync button + await page.click('button:has-text("Sinkronkan Sekarang")'); + + // Verify success message + await expect(page.locator("text=Sinkronisasi berhasil dilakukan")).toBeVisible(); + + // Verify timestamp updated (it should show "beberapa detik yang lalu" or similar because of dayjs fromNow) + // We can just check if the new time format is there or the relative time updated + await expect(page.locator("text=beberapa detik yang lalu")).toBeVisible(); + }); + + test("should handle synchronization error", async ({ page }) => { + await page.goto("/pengaturan/sinkronisasi"); + + // Mock the sync API failure + await page.route("**/api/noc/sync", async (route) => { + if (route.request().method() === "POST") { + await route.fulfill({ + status: 200, // API returns 200 but with success: false for business logic errors + contentType: "application/json", + body: JSON.stringify({ + success: false, + error: "Sinkronisasi gagal dijalankan", + }), + }); + } + }); + + // Click Sync button + await page.click('button:has-text("Sinkronkan Sekarang")'); + + // Verify error message + await expect(page.locator("text=Sinkronisasi gagal dijalankan")).toBeVisible(); + }); +});