236 lines
7.1 KiB
TypeScript
236 lines
7.1 KiB
TypeScript
/// <reference types="cypress" />
|
|
|
|
const apiResponse = (data: Record<string, unknown>) => ({
|
|
success: true,
|
|
message: "ok",
|
|
data,
|
|
});
|
|
|
|
const visitDashboard = () => {
|
|
cy.visit("/", {
|
|
onBeforeLoad(win) {
|
|
cy.stub(win, "matchMedia").callsFake((query: string) => ({
|
|
matches: query === "(prefers-reduced-motion: reduce)",
|
|
media: query,
|
|
onchange: null,
|
|
addEventListener: () => undefined,
|
|
removeEventListener: () => undefined,
|
|
addListener: () => undefined,
|
|
removeListener: () => undefined,
|
|
dispatchEvent: () => false,
|
|
}));
|
|
},
|
|
});
|
|
};
|
|
|
|
const stubDashboardRequests = () => {
|
|
const now = Math.floor(Date.now() / 1000);
|
|
|
|
cy.intercept("GET", "**/api/proxy/dashboard/summary**", (req) => {
|
|
const days = Number(req.query.days);
|
|
expect(req.query.closing_window).to.eq("168");
|
|
req.alias = days === 30 ? "summary30Days" : "summary14Days";
|
|
req.reply({
|
|
statusCode: 200,
|
|
body: apiResponse({
|
|
total_tenders: 120,
|
|
active_tenders: 72,
|
|
awarded_tenders: 18,
|
|
expired_tenders: 20,
|
|
cancelled_tenders: 10,
|
|
closing_soon: 12,
|
|
total_estimated_value: 2500000,
|
|
value_currency: "EUR",
|
|
days,
|
|
scraped_ted: [
|
|
{ date: "2026-06-08", count: days === 30 ? 12 : 5 },
|
|
{ date: "2026-06-09", count: days === 30 ? 18 : 8 },
|
|
],
|
|
generated_at: now,
|
|
}),
|
|
});
|
|
});
|
|
|
|
cy.intercept("GET", "**/api/proxy/dashboard/trend**", (req) => {
|
|
expect(req.query).to.include({ days: "14", metric: "created" });
|
|
req.reply({
|
|
statusCode: 200,
|
|
body: apiResponse({
|
|
metric: "created",
|
|
days: 14,
|
|
series: [
|
|
{ date: "2026-06-07", count: 4 },
|
|
{ date: "2026-06-08", count: 7 },
|
|
{ date: "2026-06-09", count: 9 },
|
|
],
|
|
}),
|
|
});
|
|
}).as("dashboardTrend");
|
|
|
|
cy.intercept("GET", "**/api/proxy/dashboard/countries**", (req) => {
|
|
expect(req.query.limit).to.eq("6");
|
|
req.reply({
|
|
statusCode: 200,
|
|
body: apiResponse({
|
|
total: 120,
|
|
items: [
|
|
{ country_code: "DE", count: 60 },
|
|
{ country_code: "FR", count: 30 },
|
|
{ country_code: "NL", count: 10 },
|
|
],
|
|
other_count: 20,
|
|
}),
|
|
});
|
|
}).as("dashboardCountries");
|
|
|
|
cy.intercept("GET", "**/api/proxy/dashboard/notice-types**", {
|
|
statusCode: 200,
|
|
body: apiResponse({
|
|
total: 100,
|
|
items: [
|
|
{ type: "CONTRACT_NOTICE", count: 70 },
|
|
{ type: "AWARD_NOTICE", count: 30 },
|
|
],
|
|
}),
|
|
}).as("dashboardNoticeTypes");
|
|
|
|
cy.intercept("GET", "**/api/proxy/dashboard/closing-soon**", (req) => {
|
|
expect(req.query.limit).to.eq("5");
|
|
req.reply({
|
|
statusCode: 200,
|
|
body: apiResponse({
|
|
items: [
|
|
{
|
|
id: "closing-tender-1",
|
|
title: "Railway signaling upgrade",
|
|
country_code: "DE",
|
|
buyer_name: "Federal Rail Agency",
|
|
submission_deadline: now + 36 * 60 * 60,
|
|
status: "active",
|
|
},
|
|
],
|
|
}),
|
|
});
|
|
}).as("dashboardClosingSoon");
|
|
|
|
cy.intercept("GET", "**/api/proxy/tenders**", (req) => {
|
|
expect(req.query).to.include({
|
|
sort_by: "created_at",
|
|
sort_order: "desc",
|
|
offset: "0",
|
|
limit: "5",
|
|
});
|
|
req.reply({
|
|
statusCode: 200,
|
|
body: apiResponse({
|
|
tenders: [
|
|
{
|
|
id: "recent-tender-1",
|
|
title: "Hospital equipment framework",
|
|
country_code: "FR",
|
|
status: "active",
|
|
created_at: now - 60 * 60,
|
|
buyer_organization: { name: "Central Health Buyer" },
|
|
},
|
|
],
|
|
}),
|
|
});
|
|
}).as("recentTenders");
|
|
|
|
cy.intercept("GET", "**/api/proxy/dashboard/statistics**", (req) => {
|
|
const days = Number(req.query.days);
|
|
req.alias = days === 30 ? "statistics30Days" : "statistics14Days";
|
|
req.reply({
|
|
statusCode: 200,
|
|
body: apiResponse({
|
|
days,
|
|
generated_at: now,
|
|
daily: {
|
|
scraped_documents: [
|
|
{ date: "2026-06-08", count: days === 30 ? 40 : 20 },
|
|
{ date: "2026-06-09", count: days === 30 ? 60 : 30 },
|
|
],
|
|
translated_notices: [
|
|
{ date: "2026-06-08", count: days === 30 ? 7 : 3 },
|
|
{ date: "2026-06-09", count: days === 30 ? 11 : 6 },
|
|
],
|
|
},
|
|
totals: {
|
|
scraped_documents: days === 30 ? 9876 : 4321,
|
|
translated_tenders: days === 30 ? 654 : 321,
|
|
scraped_ted_notices: days === 30 ? 2468 : 1234,
|
|
},
|
|
}),
|
|
});
|
|
});
|
|
};
|
|
|
|
describe("tender dashboard", () => {
|
|
beforeEach(() => {
|
|
cy.setAuthCookies();
|
|
stubDashboardRequests();
|
|
});
|
|
|
|
it("renders the new dashboard sections with API data and detail links", () => {
|
|
visitDashboard();
|
|
|
|
cy.wait([
|
|
"@summary14Days",
|
|
"@dashboardTrend",
|
|
"@dashboardCountries",
|
|
"@dashboardNoticeTypes",
|
|
"@dashboardClosingSoon",
|
|
"@recentTenders",
|
|
"@statistics14Days",
|
|
]);
|
|
|
|
cy.get('h1[aria-label*="welcome to OppLens"]')
|
|
.should("be.visible")
|
|
.and("have.attr", "aria-label")
|
|
.and("include", "welcome to OppLens");
|
|
|
|
cy.contains("Total Tenders").parent().should("contain.text", "120");
|
|
cy.contains("Active Now").parent().should("contain.text", "72");
|
|
cy.contains("Closing Soon").parent().should("contain.text", "12");
|
|
cy.contains("Estimated Value").parent().should("contain.text", "€2.5M");
|
|
|
|
cy.contains("Tender Flow — Last 14 days").should("be.visible");
|
|
cy.contains("By Country").should("be.visible");
|
|
cy.contains(/^DE$/).should("be.visible");
|
|
cy.contains("Contract Notice").should("be.visible");
|
|
cy.contains("+70 · 70%").should("be.visible");
|
|
|
|
cy.contains("Railway signaling upgrade")
|
|
.closest("a")
|
|
.should("have.attr", "href", "/tenders/closing-tender-1");
|
|
cy.contains("Hospital equipment framework")
|
|
.closest("a")
|
|
.should("have.attr", "href", "/tenders/recent-tender-1");
|
|
|
|
cy.contains("Scraping & Translation").should("be.visible");
|
|
cy.contains("Total Scraped Documents").parent().should("contain.text", "4,321");
|
|
cy.contains("Total Translated Tenders").parent().should("contain.text", "321");
|
|
cy.contains("Scraped TED per day").should("be.visible");
|
|
cy.contains("Scraped documents per day").should("be.visible");
|
|
cy.contains("Translated notices per day").should("be.visible");
|
|
});
|
|
|
|
it("reloads scraping and translation statistics for a selected range", () => {
|
|
visitDashboard();
|
|
cy.wait(["@summary14Days", "@statistics14Days"]);
|
|
|
|
cy.contains("button", "14d").should("have.class", "bg-primary");
|
|
cy.contains("button", "30d").click();
|
|
cy.wait("@summary30Days")
|
|
.its("request.query.days")
|
|
.should("eq", "30");
|
|
cy.wait("@statistics30Days")
|
|
.its("request.query.days")
|
|
.should("eq", "30");
|
|
|
|
cy.contains("button", "30d").should("have.class", "bg-primary");
|
|
cy.contains("Total Scraped Documents").parent().should("contain.text", "9,876");
|
|
cy.contains("Total Translated Tenders").parent().should("contain.text", "654");
|
|
});
|
|
});
|