Skip to content
Open
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
63 changes: 63 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"parser": "@typescript-eslint/parser",
"env": {
"node": true,
"browser": true,
"commonjs": true,
"es2021": true,
"mocha": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"google",
"prettier",
"plugin:json/recommended"
],
"overrides": [
{
"files": ["test/**/*.js", "**/*.json", "cypress/**/*.js", "plugins/**/*.js"],
"parserOptions": {
"project": null
},
"parser": "espree",
"env": {
"cypress/globals": true
},
"plugins": ["cypress"],
"rules": {
"@typescript-eslint/no-unused-expressions": "off"
}
}
],
"parserOptions": {
"project": "./tsconfig.json",
"requireConfigFile": false,
"ecmaVersion": 12,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true,
"modules": true
},
"babelOptions": {
"presets": ["@babel/preset-react"]
}
},
"plugins": ["@typescript-eslint", "react", "prettier"],
"rules": {
"react/prop-types": "off",
"require-jsdoc": "off",
"no-async-promise-executor": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-require-imports": "off",
"@typescript-eslint/no-unused-expressions": "off"
},
"settings": {
"react": {
"version": "detect"
}
},
"ignorePatterns": ["src/config/generated/config.ts"]
}
143 changes: 143 additions & 0 deletions cypress/e2e/tagPush.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/**
* 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.
*/

describe('Tag Push Functionality', () => {
beforeEach(() => {
cy.login('admin', 'admin');
cy.on('uncaught:exception', () => false);

// Create test data for tag pushes
cy.createTestTagPush();
});

describe('Tag Push Display in PushesTable', () => {
it('can navigate to push dashboard and view push table', () => {
cy.visit('/dashboard/push');

// Wait for API call to complete
cy.wait('@getPushes');

// Check that we can see the basic table structure
cy.get('table', { timeout: 10000 }).should('exist');
cy.get('thead').should('exist');
cy.get('tbody').should('exist');

// Now we should have test data, so we can check for rows
cy.get('tbody tr').should('have.length.at.least', 1);

// Check the structure of the first row
cy.get('tbody tr')
.first()
.within(() => {
cy.get('td').should('have.length.at.least', 6); // We know there are multiple columns
// Check for tag-specific content
cy.contains('v1.0.0').should('exist'); // Tag name
cy.contains('test-tagger').should('exist'); // Tagger
});
});

it('has search functionality', () => {
cy.visit('/dashboard/push');
cy.wait('@getPushes');

// Check search input exists
cy.get('input[type="text"]').first().should('exist');

// Test searching for tag name
cy.get('input[type="text"]').first().type('v1.0.0');
cy.get('tbody tr').should('have.length.at.least', 1);
});

it('can interact with push table entries', () => {
cy.visit('/dashboard/push');
cy.wait('@getPushes');

cy.get('tbody tr').should('have.length.at.least', 1);

// Check for clickable elements in the first row
cy.get('tbody tr')
.first()
.within(() => {
// Should have links and buttons
cy.get('a').should('have.length.at.least', 1); // Repository links, etc.
cy.get('button').should('have.length.at.least', 1); // Action button
});
});
});

describe('Tag Push Details Page', () => {
it('can access push details page structure', () => {
// Try to access a push details page directly
cy.visit('/dashboard/push/test-push-id', { failOnStatusCode: false });

// Check basic page structure exists (regardless of whether push exists)
cy.get('body').should('exist'); // Basic content check

// If we end up redirected, that's also acceptable behavior
cy.url().should('include', '/dashboard');
});
});

describe('Basic UI Navigation', () => {
it('can navigate between dashboard pages', () => {
cy.visit('/dashboard/push');
cy.wait('@getPushes');
cy.get('table', { timeout: 10000 }).should('exist');

// Test navigation to repo dashboard
cy.visit('/dashboard/repo');
cy.get('table', { timeout: 10000 }).should('exist');

// Test navigation to user management if it exists
cy.visit('/dashboard/user');
cy.get('body').should('exist');
});
});

describe('Application Robustness', () => {
it('handles navigation to non-existent push gracefully', () => {
// Try to visit a non-existent push detail page
cy.visit('/dashboard/push/non-existent-push-id', { failOnStatusCode: false });

// Should either redirect or show error page, but not crash
cy.get('body').should('exist');
});

it('maintains functionality after page refresh', () => {
cy.visit('/dashboard/push');
cy.wait('@getPushes');
cy.get('table', { timeout: 10000 }).should('exist');

// Refresh the page
cy.reload();
// Wait for API call again after reload
cy.wait('@getPushes');

// Wait for page to reload and check basic functionality
cy.get('body').should('exist');

// Give more time for table to load after refresh, or check if redirected
cy.url().then((url) => {
if (url.includes('/dashboard/push')) {
cy.get('table', { timeout: 15000 }).should('exist');
} else {
// If redirected (e.g., to login), that's also acceptable behavior
cy.get('body').should('exist');
}
});
});
});
});
63 changes: 63 additions & 0 deletions cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,66 @@ Cypress.Commands.add('getCSRFToken', () => {
return cy.wrap(decodeURIComponent(token));
});
});

Cypress.Commands.add('createTestTagPush', (pushData = {}) => {
const defaultTagPush = {
id: `test-tag-push-${Date.now()}`,
steps: [],
error: false,
blocked: true,
allowPush: false,
authorised: false,
canceled: false,
rejected: false,
autoApproved: false,
autoRejected: false,
type: 'push',
method: 'get',
timestamp: Date.now(),
project: 'cypress-test',
repoName: 'test-repo.git',
url: 'https://github.com/cypress-test/test-repo.git',
repo: 'cypress-test/test-repo.git',
user: 'test-tagger',
userEmail: 'test-tagger@test.com',
branch: 'refs/heads/main',
tag: 'refs/tags/v1.0.0',
commitFrom: '0000000000000000000000000000000000000000',
commitTo: 'abcdef1234567890abcdef1234567890abcdef12',
lastStep: null,
blockedMessage: '\n\n\nGitProxy has received your tag push\n\n\n',
_id: null,
attestation: null,
tagData: [
{
tagName: 'v1.0.0',
type: 'annotated',
tagger: 'test-tagger',
message: 'Release version 1.0.0\n\nThis is a test tag release for Cypress testing.',
timestamp: Math.floor(Date.now() / 1000),
},
],
commitData: [
{
commitTs: Math.floor(Date.now() / 1000) - 300,
commitTimestamp: Math.floor(Date.now() / 1000) - 300,
message: 'feat: add new tag push feature',
committer: 'test-committer',
author: 'test-author',
authorEmail: 'test-author@test.com',
},
],
diff: {
content: '+++ test tag push implementation',
},
...pushData,
};

// For now, intercept the push API calls and return our test data
cy.intercept('GET', '**/api/v1/push*', {
statusCode: 200,
body: [defaultTagPush],
}).as('getPushes');

return cy.wrap(defaultTagPush);
});
Loading
Loading