Skip to content

Commit

Permalink
test(e2e): implement user auth flow with token strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
hdinia committed Jan 24, 2025
1 parent 7065686 commit a3e9d81
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 23 deletions.
5 changes: 5 additions & 0 deletions webapp/cypress/cypress.env.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"TEST_USER": "testuser",
"TEST_PASSWORD": "testpass",
"AUTH_TOKEN": ""
}
72 changes: 52 additions & 20 deletions webapp/cypress/e2e/studies.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,28 @@

describe("Studies Page", () => {
beforeEach(() => {
// Mock API response for studies
cy.intercept("GET", "/v1/studies", {
statusCode: 200,
body: [
{ id: "1", name: "Study 1", description: "First study" },
{ id: "2", name: "Study 2", description: "Second study" },
],
}).as("getStudies");
cy.login().then(() => {
// Mock studies API response with auth header
cy.intercept(
{
method: "GET",
url: "/v1/studies",
headers: {
Authorization: `Bearer ${Cypress.env("AUTH_TOKEN")}`,
},
},
{
statusCode: 200,
body: [
{ id: "1", name: "Study 1", description: "First study" },
{ id: "2", name: "Study 2", description: "Second study" },
],
},
).as("getStudies");

cy.visit("/studies");
cy.visit("/studies");
cy.wait("@getStudies");
});
});

it("should display the studies page correctly", () => {
Expand All @@ -48,23 +60,43 @@ describe("Studies Page", () => {
});

it("should handle loading state", () => {
// Delay the API response to test loading state
cy.intercept("GET", "/v1/studies", (req) => {
req.reply({
delay: 1000,
body: [],
});
}).as("delayedStudies");
// Re-mock with delay for this specific test
cy.intercept(
{
method: "GET",
url: "/v1/studies",
headers: {
Authorization: `Bearer ${Cypress.env("AUTH_TOKEN")}`,
},
},
(req) => {
req.reply({
delay: 1000,
body: [],
});
},
).as("delayedStudies");

cy.visit("/studies");
cy.get('[data-testid="loading-spinner"]').should("exist");
});

it("should handle error state", () => {
// Force API error
cy.intercept("GET", "/v1/studies", {
forceNetworkError: true,
}).as("errorStudies");
// Re-mock with error for this specific test
cy.intercept(
{
method: "GET",
url: "/v1/studies",
headers: {
Authorization: `Bearer ${Cypress.env("AUTH_TOKEN")}`,
},
},
{
forceNetworkError: true,
},
).as("errorStudies");

cy.visit("/studies");
cy.contains("Failed to load studies").should("exist");
cy.get("button").contains("Refresh").should("exist");
});
Expand Down
100 changes: 100 additions & 0 deletions webapp/cypress/support/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/**
* Copyright (c) 2025, RTE (https://www.rte-france.com)
*
* See AUTHORS.txt
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* SPDX-License-Identifier: MPL-2.0
*
* This file is part of the Antares project.
*/

import "@testing-library/cypress/add-commands";

declare global {
namespace Cypress {
interface Chainable {
/**
* Full authentication flow with proper user creation and login validation
*
* @example cy.login()
*/
login(username?: string, password?: string): Chainable<void>;
}
}
}

Cypress.Commands.add(
"login",
(
username = Cypress.env("AUTH_USER") || "testuser",
password = Cypress.env("AUTH_PASSWORD") || "testpass",
) => {
cy.session([username, password], () => {
// 1. Verify/Create test user
cy.request({
method: "POST",
url: "/v1/users",
body: {
name: username,
password: password,
},
failOnStatusCode: false,
}).then((userResponse) => {
if (userResponse.status === 401) {
throw new Error("User creation failed: Unauthorized");
}

if (userResponse.status === 409) {
cy.log("User already exists, proceeding with login");
} else if (userResponse.status >= 400) {
throw new Error(`User creation failed: ${userResponse.body.detail}`);
}
});

// 2. Login with credentials
cy.request({
method: "POST",
url: "/v1/login",
body: {
name: username,
password: password,
},
failOnStatusCode: false,
}).then((loginResponse) => {
if (loginResponse.status !== 200) {
throw new Error(`Login failed: ${loginResponse.body.detail}`);
}

// 3. Create bot token with authenticated session
const cookies = loginResponse.headers["set-cookie"];
cy.request({
method: "POST",
url: "/v1/bots",
headers: {
Cookie: Array.isArray(cookies) ? cookies.join("; ") : cookies || "",
"Content-Type": "application/json",
},
body: {
name: `cypress-bot-${Date.now()}`,
roles: [{ group: "cypress-tests", role: 0 }],
is_author: true,
},
}).then((botResponse) => {
window.localStorage.setItem("authToken", botResponse.body.token);
Cypress.env("AUTH_TOKEN", botResponse.body.token);
});
});

// 4. Validate UI flow
cy.visit("/");
cy.get('input[name="name"]').type(username);
cy.get('input[name="password"]').type(password, { log: false });
cy.get('button[type="submit"]').click();
cy.url().should("include", "/studies");
});
},
);
35 changes: 32 additions & 3 deletions webapp/cypress/support/e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,45 @@
* This file is part of the Antares project.
*/

import "./auth";
import "@testing-library/cypress/add-commands";

const AppPages = {
studies: "/studies",
settings: "/settings",
} as const;

declare global {
namespace Cypress {
interface Chainable {
login(username: string, password: string): Chainable<void>;
/**
* Navigate to a specific page in the application
*
* @example cy.navigateTo('studies')
*/
navigateTo(page: keyof typeof AppPages): Chainable<void>;

/**
* Check main application header visibility
*
* @example cy.checkHeader()
*/
checkHeader(): Chainable<void>;
}
}
}

Cypress.Commands.add("login", (username, password) => {
// TODO: add login command logic
Cypress.Commands.add("navigateTo", (page) => {
const path = AppPages[page];

cy.location("pathname").then((currentPath) => {
if (currentPath !== path) {
cy.visit(path);
}
});
});

// Global intercepts for common API calls
beforeEach(() => {
cy.intercept("GET", "/v1/health", { statusCode: 200 }).as("healthCheck");
});

0 comments on commit a3e9d81

Please sign in to comment.