Skip to content
Draft
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 .github/workflows/cypress.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ jobs:
with:
install: false
build: pnpm cypress:build
start: pnpm start
start: pnpm cypress:start
wait-on: 'http://localhost:5055'
record: true
env:
Expand Down
32 changes: 32 additions & 0 deletions cypress.config.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,48 @@
import { ChildProcess, spawn } from 'child_process';
import { defineConfig } from 'cypress';
import path from 'path';

let oidcServerProcess: ChildProcess | null = null;

export default defineConfig({
projectId: 'onnqy3',
e2e: {
baseUrl: 'http://localhost:5055',
video: true,
setupNodeEvents(on) {
on('task', {
startOidcServer() {
if (oidcServerProcess) return null;
const serverFile = path.join(
__dirname,
'cypress/support/oidc-server.mts'
);
oidcServerProcess = spawn('node', [serverFile], {
stdio: 'inherit',
detached: false,
});
return new Promise((resolve) =>
setTimeout(() => resolve(null), 2000)
);
},
stopOidcServer() {
if (oidcServerProcess) {
oidcServerProcess.kill();
oidcServerProcess = null;
}
return null;
},
});
},
},
env: {
ADMIN_EMAIL: 'admin@seerr.dev',
ADMIN_PASSWORD: 'test1234',
USER_EMAIL: 'friend@seerr.dev',
USER_PASSWORD: 'test1234',
OIDC_ISSUER_URL: 'http://localhost:8092',
OIDC_CLIENT_ID: 'jellyseerr-test',
OIDC_CLIENT_SECRET: 'test-secret',
},
retries: {
runMode: 2,
Expand Down
112 changes: 111 additions & 1 deletion cypress/e2e/login.cy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
describe('Login Page', () => {
import { MediaServerType } from '../../server/constants/server';

describe('Login', () => {
it('succesfully logs in as an admin', () => {
cy.loginAsAdmin();
cy.visit('/');
Expand All @@ -11,3 +13,111 @@ describe('Login Page', () => {
cy.contains('Trending');
});
});

/**
* Generates all permutations of boolean values of the given length.
* @param n Length
*/
function permutations(n: number): boolean[][] {
return Array.from({ length: 1 << n }, (_, i) =>
Array.from({ length: n }, (_, j) => !!(i & (1 << (n - 1 - j))))
);
}

describe('Login Page', () => {
const testProvider = {
slug: 'test-provider',
name: 'Test Provider',
issuerUrl: Cypress.env('OIDC_ISSUER_URL'),
clientId: Cypress.env('OIDC_CLIENT_ID'),
clientSecret: Cypress.env('OIDC_CLIENT_SECRET'),
newUserLogin: true,
};

const LOCAL_LOGIN_SELECTOR = '[data-testid^="seerr-login"]';
const PLEX_LOGIN_SELECTOR = '[data-testid="plex-login-button"]';
const MEDIA_SERVER_LOGIN_SELECTOR = '[data-testid^="mediaserver-login"]';
const OIDC_LOGIN_SELECTOR = `[data-testid="oidc-login-${testProvider.slug}"]`;

before(() => {
cy.loginAsAdmin();
// Configure an OIDC Provider to show on the login screen
cy.request({
method: 'POST',
url: '/api/v1/settings/main',
body: {
oidcLogin: true,
},
});
cy.configureOidcProvider(testProvider);
});

after(() => {
cy.loginAsAdmin();
// Reset settings to defaults
cy.request({
method: 'POST',
url: '/api/v1/settings/main',
body: {
localLogin: true,
mediaServerLogin: true,
oidcLogin: false,
mediaServerType: MediaServerType.PLEX,
},
});
// Remove created OIDC provider
cy.request({
method: 'DELETE',
url: `/api/v1/settings/oidc/${testProvider.slug}`,
});
});

for (const [
localLogin,
mediaServerLogin,
oidcLogin,
enablePlex,
] of permutations(4)) {
if (!localLogin && !mediaServerLogin && !oidcLogin) continue;

const enabledFlags = Object.entries({
localLogin,
mediaServerLogin,
oidcLogin,
})
.filter(([_, v]) => v)
.map(([k, _]) => k)
.concat(enablePlex ? 'plex' : 'jellyfin')
.join(', ');

it(`correct items are shown (${enabledFlags})`, () => {
cy.loginAsAdmin();
// set settings
cy.request({
method: 'POST',
url: '/api/v1/settings/main',
body: {
localLogin,
mediaServerLogin,
oidcLogin,
mediaServerType: enablePlex
? MediaServerType.PLEX
: MediaServerType.JELLYFIN,
},
});
cy.then(Cypress.session.clearCurrentSessionData);

cy.visit('/login');

// Ensure the right things are visible
cy.get(LOCAL_LOGIN_SELECTOR).should(localLogin ? 'exist' : 'not.exist');
cy.get(MEDIA_SERVER_LOGIN_SELECTOR).should(
mediaServerLogin && !enablePlex ? 'exist' : 'not.exist'
);
cy.get(PLEX_LOGIN_SELECTOR).should(
mediaServerLogin && enablePlex ? 'exist' : 'not.exist'
);
cy.get(OIDC_LOGIN_SELECTOR).should(oidcLogin ? 'exist' : 'not.exist');
});
}
});
144 changes: 144 additions & 0 deletions cypress/e2e/oidc/configure-provider.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
describe('OIDC Provider Configuration', () => {
const testProvider = {
slug: 'test-provider',
name: 'Test Provider',
issuerUrl: Cypress.env('OIDC_ISSUER_URL'),
clientId: Cypress.env('OIDC_CLIENT_ID'),
clientSecret: Cypress.env('OIDC_CLIENT_SECRET'),
newUserLogin: true,
};

before(() => {
cy.task('startOidcServer');
});

after(() => {
cy.task('stopOidcServer');
});

beforeEach(() => {
cy.loginAsAdmin();
// Clean up any existing test provider and disable OIDC
cy.deleteOidcProvider(testProvider.slug);
cy.request({
method: 'POST',
url: '/api/v1/settings/main',
body: { oidcLogin: false },
});
});

it('should open when OIDC enabled', () => {
cy.visit('/settings/users');

// Enable OIDC login checkbox
cy.get('input[name="oidcLogin"]').check();

// Verify the dialog is open
cy.contains('OpenID Connect').should('be.visible');
});

it('should manually close and open', () => {
cy.visit('/settings/users');

// Enable OIDC login checkbox
cy.get('input[name="oidcLogin"]').check();

// Verify the dialog is open
cy.get('[data-testid="settings-oidc"]').should('be.visible');

// Close the dialog
cy.contains('Close').click();
cy.get('[data-testid="settings-oidc"]').should('not.exist');

cy.get('[data-testid="oidc-settings-button"]');
});

it('should add a new OIDC provider', () => {
cy.visit('/settings/users');

// Enable OIDC login
cy.get('input[name="oidcLogin"]').check();

// Click add provider button
cy.contains('button', 'Add').click();

const getModal = () => cy.get('[data-testid="edit-oidc-modal"]');

// Fill in provider details
getModal().get('input[name="name"]').type(testProvider.name);
getModal().get('input[name="issuerUrl"]').type(testProvider.issuerUrl);
getModal().get('input[name="clientId"]').type(testProvider.clientId);
getModal()
.get('input[name="clientSecret"]')
.type(testProvider.clientSecret);

// Open Advanced Settings section
getModal().contains('button', 'Advanced Settings').click();

// Set slug
getModal()
.get('input[name="slug"]')
.scrollIntoView()
.clear()
.type(testProvider.slug);

// Enable new user login
getModal()
.get('input[name="newUserLogin"]')
.scrollIntoView()
.check({ force: true });

// Save the provider
getModal().contains('button', 'Save Changes').scrollIntoView().click();

// Verify provider appears in the list
cy.contains(testProvider.name).should('be.visible');
});

it('should edit an existing OIDC provider', () => {
// First create a provider via API
cy.configureOidcProvider(testProvider);

cy.visit('/settings/users');

// Enable OIDC login
cy.get('input[name="oidcLogin"]').check();

// Click on the provider to edit
cy.contains('li', testProvider.name).contains('button', 'Edit').click();

const getModal = () => cy.get('[data-testid="edit-oidc-modal"]');

// Update the name
const updatedName = 'Updated Test Provider';
getModal().get('input[name="name"]').clear().type(updatedName);

// Save changes
getModal().contains('button', 'Save Changes').click();

// Verify updated name appears
cy.contains(updatedName).should('be.visible');
});

it('should delete an OIDC provider', () => {
// First create a provider via API
cy.configureOidcProvider(testProvider);

cy.visit('/settings/users');

// Enable OIDC login
cy.get('input[name="oidcLogin"]').check();

// Click on the provider to edit
cy.contains(testProvider.name).click();

// Click delete button once to initiate
cy.contains('button', 'Delete').click();

// Click "Are you sure?" to confirm deletion
cy.contains('button', 'Are you sure?').click();

// Verify provider is removed from the list
cy.contains(testProvider.name).should('not.exist');
});
});
Loading
Loading