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
2 changes: 1 addition & 1 deletion components/navigation/NavigationBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
</div>
</label>
<ul tabindex="0" class="p-2 mt-3 shadow menu dropdown-content bg-base-100 rounded-box w-52">
<li v-if="user.admin"><NuxtLink to="/admin/settings/backend">
<li v-if="user.admin" data-cy="admin-settings-link"><NuxtLink to="/admin/settings/backend">
Admin Settings
</NuxtLink></li>
<li><a @click="logoutUser()">Logout {{ user.username }}</a></li>
Expand Down
2 changes: 1 addition & 1 deletion composables/states.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { PublicSettings, Category, TokenResponse, TorrentTag } from "torrust-index-types-lib";
import { Rest } from "torrust-index-api-lib";
import { useRuntimeConfig, useState } from "#app";
import { notify } from "notiwind-ts";
import { useRuntimeConfig, useState } from "#app";

export const useRestApi = () => useState<Rest>("rest-api", () => new Rest(useRuntimeConfig().public.apiBase));
export const useCategories = () => useState<Array<Category>>("categories", () => new Array<Category>());
Expand Down
17 changes: 16 additions & 1 deletion cypress.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
import { defineConfig } from "cypress";
import { grantAdminRole } from "./cypress/e2e/contexts/user/tasks";
import { DatabaseConfig } from "./cypress/e2e/common/database";

function databaseConfig (config: Cypress.PluginConfigOptions): DatabaseConfig {
return {
filepath: config.env.db_file_path
};
}

export default defineConfig({
e2e: {
baseUrl: "http://localhost:3000",
setupNodeEvents (on, config) {
// implement node event listeners here
on("task", {
grantAdminRole: ({ username }) => {
return grantAdminRole(username, databaseConfig(config));
}
});
}
},
env: {
db_file_path: "./storage/database/torrust_index_backend_e2e_testing.db"
}
});
33 changes: 33 additions & 0 deletions cypress/e2e/common/database.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Database } from "sqlite3";

export interface DatabaseConfig {
filepath: string; // Relative path from project root to the SQLite database file
}
export interface DatabaseQuery {
query: string;
params: Array<string | number | boolean>;
}

export const runDatabaseQuery = ({ query, params }: DatabaseQuery, config: DatabaseConfig): Promise<any> => {
return new Promise((resolve, reject) => {
const db = new Database(config.filepath, (err) => {
if (err) {
reject(err.message);
}
});

db.get(query, params, function (err, row) {
if (err) {
reject(err);
} else {
resolve(row);
}
});

db.close((err) => {
if (err) {
reject(err);
}
});
});
};
35 changes: 35 additions & 0 deletions cypress/e2e/contexts/user/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Custom commands for user context

// Registration

Cypress.Commands.add("register", (registration_form) => {
cy.visit("/signup");

cy.get("input[data-cy=\"registration-form-username\"]").type(registration_form.username);
cy.get("input[data-cy=\"registration-form-email\"]").type(registration_form.email);
cy.get("input[data-cy=\"registration-form-password\"]").type(registration_form.password);
cy.get("input[data-cy=\"registration-form-confirm-password\"]").type(registration_form.confirm_password);

cy.get("button[data-cy=\"registration-form-submit\"]").click();

cy.contains("Your account was registered!");
});

Cypress.Commands.add("register_as_admin", (registration_form) => {
cy.register(registration_form);

cy.task("grantAdminRole", { username: registration_form.username });
});

// Authentication

Cypress.Commands.add("login", (username: string, password: string) => {
cy.visit("/signin");

cy.get("input[data-cy=\"login-form-username\"]").type(username);
cy.get("input[data-cy=\"login-form-password\"]").type(password);

cy.get("button[data-cy=\"login-form-submit\"]").click();

cy.url().should("include", "/torrents");
});
19 changes: 19 additions & 0 deletions cypress/e2e/contexts/user/registration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export type RegistrationForm = {
username: string
email: string
password: string
confirm_password: string
}

export function random_user_registration_data (): RegistrationForm {
return {
username: `user${random_user_id()}`,
email: `user${random_user_id()}@example.com`,
password: "12345678",
confirm_password: "12345678"
};
}

function random_user_id (): number {
return Math.floor(Math.random() * 1000000);
}
41 changes: 41 additions & 0 deletions cypress/e2e/contexts/user/specs/authentication.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { random_user_registration_data } from "../registration";

describe("A registered user", () => {
beforeEach(() => {
cy.visit("/");
});

it("should be able to sign in", () => {
cy.visit("/signup");

const registration_form = random_user_registration_data();

cy.register(registration_form);

cy.visit("/signin");

cy.get("input[data-cy=\"login-form-username\"]").type(registration_form.username);
cy.get("input[data-cy=\"login-form-password\"]").type(registration_form.password);

cy.get("button[data-cy=\"login-form-submit\"]").click();

cy.url().should("include", "/torrents");
});
});

describe("The website admin", () => {
beforeEach(() => {
cy.visit("/");
});

it("should be able to sign in as admin", () => {
const registration_form = random_user_registration_data();

cy.register_as_admin(registration_form);

cy.login(registration_form.username, registration_form.password);

// If the user is an admin, the link to admin settings should be available
cy.get("li[data-cy=\"admin-settings-link\"]");
});
});
Original file line number Diff line number Diff line change
@@ -1,35 +1,15 @@
type RegistrationForm = {
username: string
email: string
password: string
confirm_password: string
}

function random_user_registration_form (): RegistrationForm {
return {
username: `user${random_user_id()}`,
email: `user${random_user_id()}@example.com`,
password: "12345678",
confirm_password: "12345678"
};
}

function random_user_id (): number {
return Math.floor(Math.random() * 1000000);
}
import { random_user_registration_data } from "../registration";

describe("A guest", () => {
beforeEach(() => {
cy.visit("/");
});

it("should be able to sign up", () => {
cy.visit("/signup");
const registration_form = random_user_registration_data();

const registration_form = random_user_registration_form();
cy.visit("/signup");

// See Cypress Docs -> Best Practices -> Selecting Elements
// https://docs.cypress.io/guides/references/best-practices#Selecting-Elements
cy.get("input[data-cy=\"registration-form-username\"]").type(registration_form.username);
cy.get("input[data-cy=\"registration-form-email\"]").type(registration_form.email);
cy.get("input[data-cy=\"registration-form-password\"]").type(registration_form.password);
Expand Down
36 changes: 36 additions & 0 deletions cypress/e2e/contexts/user/tasks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Custom tasks for user context

import { DatabaseConfig, DatabaseQuery, runDatabaseQuery } from "../../common/database";

// Task to grant admin role to a user by username
export const grantAdminRole = async (username: string, db_config: DatabaseConfig): Promise<number> => {
let user_id: number;

try {
const result = await runDatabaseQuery(getUserIdByUsernameQuery(username), db_config);

const user_id = result.user_id;

await runDatabaseQuery(grantAdminRoleToUserWithId(user_id), db_config);

return user_id;
} catch (err) {
return await Promise.reject(err);
}
};

// Database query specifications

function getUserIdByUsernameQuery (username: string): DatabaseQuery {
return {
query: "SELECT user_id FROM torrust_user_profiles WHERE username = ?",
params: [username]
};
}

function grantAdminRoleToUserWithId (user_id: number): DatabaseQuery {
return {
query: "UPDATE torrust_users SET administrator = ? WHERE user_id = ?",
params: [true, user_id]
};
}
51 changes: 14 additions & 37 deletions cypress/support/commands.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,14 @@
/// <reference types="cypress" />
// ***********************************************
// This example commands.ts shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
//
// declare global {
// namespace Cypress {
// interface Chainable {
// login(email: string, password: string): Chainable<void>
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
// }
// }
// }
import "../e2e/contexts/user/commands";
import { RegistrationForm } from "../e2e/contexts/user/registration";

declare global {
namespace Cypress {
interface Chainable {
// Registration
register(registration_form: RegistrationForm): Chainable<void>
register_as_admin(registration_form: RegistrationForm): Chainable<void>
// Authentication
login(username: string, password: string): Chainable<void>
}
}
}
Loading