Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -276,3 +276,4 @@ website/.docusaurus

# Generated from testing
/test/fixtures/test-package/package-lock.json
cypress/screenshots/
307 changes: 307 additions & 0 deletions CYPRESS_PLAN.md
Comment thread
dcoric marked this conversation as resolved.
Outdated

Large diffs are not rendered by default.

21 changes: 20 additions & 1 deletion cypress/e2e/docker/pushActions.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,20 @@ describe('Push Actions (Approve, Reject, Cancel)', () => {
cy.logout();
});

afterEach(() => {
afterEach(function () {
// Clean up push created in this test (if any)
if (this.pushId) {
cy.deleteTestPush(this.pushId);
}
cy.logout();
});

after(() => {
// Clean up test users
cy.deleteTestUser(testUser.username);
cy.deleteTestUser(approverUser.username);
});

describe('Approve flow', () => {
beforeEach(() => {
const suffix = `approve-${Date.now()}`;
Expand Down Expand Up @@ -182,6 +192,9 @@ describe('Push Actions (Approve, Reject, Cancel)', () => {

describe('Negative: unauthorized approve', () => {
beforeEach(() => {
// Rate-limit guard: wait to avoid 429 from rapid push creations
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(2000);
const suffix = `neg-approve-${Date.now()}`;
cy.createPush(testUser.username, testUser.password, testUser.email, suffix).as('pushId');
});
Expand Down Expand Up @@ -217,6 +230,9 @@ describe('Push Actions (Approve, Reject, Cancel)', () => {

describe('Negative: unauthorized reject', () => {
beforeEach(() => {
// Rate-limit guard: wait to avoid 429 from rapid push creations
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(2000);
Comment thread
dcoric marked this conversation as resolved.
Outdated
const suffix = `neg-reject-${Date.now()}`;
cy.createPush(testUser.username, testUser.password, testUser.email, suffix).as('pushId');
});
Expand All @@ -240,6 +256,9 @@ describe('Push Actions (Approve, Reject, Cancel)', () => {

describe('Attestation dialog cancel does not cancel the push', () => {
beforeEach(() => {
// Rate-limit guard: wait to avoid 429 from rapid push creations
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(2000);
const suffix = `dialog-cancel-${Date.now()}`;
cy.createPush(testUser.username, testUser.password, testUser.email, suffix).as('pushId');
});
Expand Down
69 changes: 69 additions & 0 deletions cypress/e2e/error-pages.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* Copyright 2026 GitProxy Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* Error Pages
* Strategy: Direct navigation. No API needed.
*/
describe('Error Pages', () => {
beforeEach(() => {
cy.login('admin', 'admin');
});

afterEach(() => {
cy.logout();
});

// --- 9.1 Unknown route shows 404 ---
it('9.1 — Unknown route shows 404 page', () => {
Comment thread
dcoric marked this conversation as resolved.
Outdated
cy.visit('/dashboard/nonexistent-page-xyz');

cy.get('[data-testid="not-found-page"]').should('be.visible');
cy.contains('404').should('be.visible');
});

// --- 9.2 Unauthorized route shows NotAuthorized ---
it('9.2 — Unauthorized route shows NotAuthorized page', () => {
// Create a non-admin user and try to access admin route
const regularUser = {
username: `errorpage_user_${Date.now()}`,
password: 'pass123',
email: `errorpage_${Date.now()}@example.com`,
gitAccount: `errorpage_git_${Date.now()}`,
};

cy.request({
method: 'POST',
url: `${Cypress.env('API_BASE_URL') || Cypress.config('baseUrl')}/api/auth/create-user`,
body: regularUser,
failOnStatusCode: false,
});

cy.clearCookies();
cy.clearLocalStorage();
cy.login(regularUser.username, regularUser.password);

// Navigate to not-authorized page directly to verify it renders
cy.visit('/not-authorized');

cy.get('[data-testid="not-authorized-page"]').should('be.visible');
cy.contains('403').should('be.visible');

// Clean up user
cy.clearCookies();
cy.deleteTestUser(regularUser.username);
});
});
103 changes: 103 additions & 0 deletions cypress/e2e/navigation.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* Copyright 2026 GitProxy Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* Navigation & Shell
* Strategy: Mix of real navigation and intercepts.
*/
describe('Navigation & Shell', () => {
beforeEach(() => {
cy.login('admin', 'admin');
});

afterEach(() => {
cy.logout();
});

// --- 8.1 Sidebar renders all visible links ---
it('8.1 — Sidebar renders all visible links', () => {
cy.visit('/dashboard/repo');

// Sidebar links should be present
cy.contains('Repositories').should('be.visible');
cy.contains('Dashboard').should('be.visible');
});

// --- 8.2 Sidebar links navigate correctly ---
it('8.2 — Sidebar links navigate correctly', () => {
cy.visit('/dashboard/repo');

// Navigate to push dashboard
cy.contains('Dashboard').click();
cy.url().should('include', '/dashboard/push');

// Navigate back to repos
cy.contains('Repositories').click();
cy.url().should('include', '/dashboard/repo');
});

// --- 8.3 Active sidebar item highlights ---
it('8.3 — Active sidebar item highlights', () => {
cy.visit('/dashboard/repo');

// The active nav link should have aria-current="page"
cy.get('[aria-current="page"]').should('exist');
});

// --- 8.4 Navbar renders ---
it('8.4 — Navbar renders correctly', () => {
cy.visit('/dashboard/repo');

cy.get('[data-testid="navbar"]').should('be.visible');
});

// --- 8.5 Footer renders ---
it('8.5 — Footer renders', () => {
cy.visit('/dashboard/repo');

cy.get('[data-testid="footer"]').should('exist');
cy.get('[data-testid="footer"]').scrollIntoView();
cy.get('[data-testid="footer"]').should('be.visible');
});

// --- 8.6 Unauthenticated user redirected ---
// NOTE: This test must run WITHOUT a login session, otherwise cy.session()
// caches authenticated cookies and restores them on cy.visit() despite
// cy.clearCookies() / cy.clearLocalStorage(). We place it in its own
// describe block that has no beforeEach login hook.

// --- 8.7 Root redirects to dashboard/repo ---
it('8.7 — / redirects to /dashboard/repo', () => {
cy.logout();
cy.visit('/');

// Root should redirect (either to login if not authenticated, or to dashboard/repo)
cy.url().should('match', /\/(login|dashboard)/);
});
});

describe('Unauthenticated access', () => {
it('8.6 — Unauthenticated user is redirected to /login', () => {
// Clear any saved Cypress sessions so no auth cookies are restored
Cypress.session.clearAllSavedSessions();
cy.clearCookies();
cy.clearLocalStorage();

cy.visit('/dashboard/profile');

cy.url().should('include', '/login');
});
});
151 changes: 151 additions & 0 deletions cypress/e2e/profile.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/**
* Copyright 2026 GitProxy Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* Profile Page
* Strategy: Real API for all tests.
*/
describe('Profile Page', () => {
const testUser = {
username: 'profile_testuser',
password: 'profile123',
email: 'profile_testuser@example.com',
gitAccount: 'profile_testuser',
};

const nonAdminUser = {
username: 'profile_regular',
password: 'regular123',
email: 'profile_regular@example.com',
gitAccount: 'profile_regular',
};

before(() => {
cy.login('admin', 'admin');
// Clean up stale users from previous interrupted runs before creating
cy.deleteTestUser(testUser.username);
cy.deleteTestUser(nonAdminUser.username);
cy.createUser(testUser.username, testUser.password, testUser.email, testUser.gitAccount);
cy.createUser(
nonAdminUser.username,
nonAdminUser.password,
nonAdminUser.email,
nonAdminUser.gitAccount,
);
// Verify users were created successfully
cy.request({
method: 'GET',
url: `${Cypress.config('baseUrl')}/api/v1/user/${testUser.username}`,
failOnStatusCode: false,
})
.its('status')
.should('eq', 200);
cy.request({
method: 'GET',
url: `${Cypress.config('baseUrl')}/api/v1/user/${nonAdminUser.username}`,
failOnStatusCode: false,
})
.its('status')
.should('eq', 200);
cy.logout();
});

after(() => {
cy.login('admin', 'admin');
cy.deleteTestUser(testUser.username);
cy.deleteTestUser(nonAdminUser.username);
cy.logout();
});

beforeEach(() => {
cy.login('admin', 'admin');
});

afterEach(() => {
cy.logout();
});

// --- 5.1 Displays user info ---
it('5.1 — Displays user info: name, role, email, GitHub username, admin status', () => {
cy.login('admin', 'admin');
cy.visit('/dashboard/profile');

cy.get('[data-testid="profile-name"]').should('be.visible');
cy.get('[data-testid="profile-role"]').should('be.visible');
cy.get('[data-testid="profile-email"]').should('be.visible');
cy.get('[data-testid="profile-gitAccount"]').should('be.visible');
cy.get('[data-testid="profile-admin-status"]').should('be.visible');
});

// --- 5.2 User can edit own GitHub username ---
it('5.2 — User can edit their own GitHub username', () => {
cy.login(testUser.username, testUser.password);
cy.visit('/dashboard/profile');

// Edit field and update button should be visible for own profile
cy.get('[data-testid="gitAccount-input"]').should('be.visible');
cy.get('[data-testid="update-profile-btn"]').should('be.visible');
});

// --- 5.3 Admin can edit another user's GitHub username ---
it("5.3 — Admin can edit another user's GitHub username", () => {
cy.login('admin', 'admin');
// Stub both the auth profile call (so AuthProvider/RouteGuard resolve quickly)
// and the target user API call to avoid UserProfile throwing on a real API error.
cy.intercept('GET', '**/api/auth/profile', {
statusCode: 200,
body: {
username: 'admin',
displayName: 'admin',
email: 'admin@place.com',
title: '',
gitAccount: 'none',
admin: true,
},
}).as('getAuthUser');
cy.intercept('GET', `**/api/v1/user/${testUser.username}`, {
statusCode: 200,
body: {
username: testUser.username,
displayName: 'Profile Test User',
email: testUser.email,
title: 'QA Tester',
gitAccount: testUser.gitAccount,
admin: false,
},
}).as('getUser');
cy.visit(`/dashboard/user/${testUser.username}`);
cy.wait('@getAuthUser');
cy.wait('@getUser');

// Wait for profile to render
cy.get('[data-testid="profile-name"]', { timeout: 10000 }).should('be.visible');

// Edit field and update button should be visible for admin viewing other user
cy.get('[data-testid="gitAccount-input"]').should('be.visible');
cy.get('[data-testid="update-profile-btn"]').should('be.visible');
});

// --- 5.4 Non-admin cannot edit other user ---
it("5.4 — Non-admin viewing another user's profile cannot edit", () => {
cy.login(nonAdminUser.username, nonAdminUser.password);
cy.visit(`/dashboard/user/${testUser.username}`);

// Edit field and update button should NOT be visible
cy.get('[data-testid="gitAccount-input"]').should('not.exist');
cy.get('[data-testid="update-profile-btn"]').should('not.exist');
});
});
Loading
Loading