Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
c3d4225
feat: add new chain for tag push
fabiovincenzi Jun 8, 2025
95e86ee
feat: add TagData to Action
fabiovincenzi Jun 8, 2025
eeb0f01
feat: handle tags in parsepush
fabiovincenzi Jun 8, 2025
87bee02
feat: handle tags in PushesTable view
fabiovincenzi Jun 8, 2025
00206ef
feat: handle tags in PushDetails view
fabiovincenzi Jun 8, 2025
098680a
chore: merge upstream
fabiovincenzi Jun 8, 2025
53bf1d0
feat: add chain tests for tags
fabiovincenzi Jun 11, 2025
53bf92d
Merge remote-tracking branch 'origin/main' into push-tags
fabiovincenzi Jun 11, 2025
b6fe22a
chore: remove lightweight tag support
fabiovincenzi Jun 11, 2025
40e01ca
Merge branch 'main' into push-tags
fabiovincenzi Jun 13, 2025
6650b70
Merge remote-tracking branch 'origin/main' into pr/fabiovincenzi/1051
fabiovincenzi Jul 22, 2025
ca77a6f
refactor: improve tag push implementation with utilities and types
fabiovincenzi Jul 22, 2025
0cb4288
refactor: improve tag push implementation with utilities and types
fabiovincenzi Jul 22, 2025
cb74281
test: add tests for tag push
fabiovincenzi Jul 22, 2025
454d5d7
fix: throw error when no commit or tag data provided
fabiovincenzi Jul 24, 2025
f3af7e7
test: add test for parsePush (tags)
fabiovincenzi Jul 24, 2025
92318e9
refactor: improve Action type safety with enums and chain selection
fabiovincenzi Jul 30, 2025
69e5c04
Merge branch 'main' into push-tags
fabiovincenzi Aug 6, 2025
63fd0bd
feat: add tag push tests
fabiovincenzi Aug 6, 2025
8b7da65
Merge branch 'main' into push-tags
fabiovincenzi Aug 6, 2025
e2afe0c
Merge remote-tracking branch 'upstream/main' into push-tags
jescalada Aug 7, 2025
4b65916
refactor: remove logs
fabiovincenzi Aug 7, 2025
b1420fc
Merge branch 'push-tags' of https://github.com/fabiovincenzi/git-prox…
fabiovincenzi Aug 7, 2025
8e8b361
feat: extract tagger timestamp and email from tag objects
fabiovincenzi Aug 7, 2025
82cc33d
Merge remote-tracking branch 'upstream/main' into push-tags
fabiovincenzi Aug 7, 2025
528631c
Merge branch 'main' into push-tags
fabiovincenzi Aug 14, 2025
38f5a07
Merge branch 'main' into push-tags
fabiovincenzi Aug 18, 2025
59c130d
feat: merge upstream/main with tag support integration
fabiovincenzi Aug 20, 2025
b969eba
fix: show diff section only for commits, not tags
fabiovincenzi Aug 20, 2025
c1d9636
fix: convert getUserProfileLink to return JSX instead of HTML strings
fabiovincenzi Aug 22, 2025
168967b
feat: merge upstream/main with tag support integration
fabiovincenzi Aug 22, 2025
d0f847d
chore: restore proxy.config.json
fabiovincenzi Aug 22, 2025
d543032
fix: remove duplicate parsePush execution in branch chain
fabiovincenzi Aug 27, 2025
abd9b2c
Merge branch 'main' into push-tags
fabiovincenzi Aug 27, 2025
279b2fb
chore: format code with npm run format
fabiovincenzi Aug 27, 2025
94939fc
Merge upstream/main into push-tags
fabiovincenzi Sep 3, 2025
e919619
test: fix tags tests
fabiovincenzi Sep 3, 2025
513b1ec
Merge branch 'main' into push-tags
fabiovincenzi Sep 3, 2025
c2e7b80
Merge branch 'main' into push-tags
fabiovincenzi Sep 10, 2025
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
127 changes: 127 additions & 0 deletions cypress/e2e/tagPush.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
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 @@ -65,3 +65,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);
});
3 changes: 3 additions & 0 deletions src/db/mongo/pushes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@ export const getPushes = async (query: PushQuery = defaultPushQuery): Promise<Ac
rejected: 1,
repo: 1,
repoName: 1,
tag: 1,
tagData: 1,
timepstamp: 1,
type: 1,
url: 1,
user: 1,
},
});
};
Expand Down
25 changes: 25 additions & 0 deletions src/db/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,31 @@ export class User {
}
}

export type Push = {
id: string;
allowPush: boolean;
authorised: boolean;
blocked: boolean;
blockedMessage: string;
branch: string;
canceled: boolean;
commitData: object;
commitFrom: string;
commitTo: string;
error: boolean;
method: string;
project: string;
rejected: boolean;
repo: string;
repoName: string;
tag?: string;
tagData?: object;
timepstamp: string;
type: string;
url: string;
user?: string;
};

export interface Sink {
getSessionStore?: () => MongoDBStore;
getPushes: (query: PushQuery) => Promise<Action[]>;
Expand Down
29 changes: 24 additions & 5 deletions src/proxy/actions/Action.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
import { processGitURLForNameAndOrg, processUrlPath } from '../routes/helper';
import { Step } from './Step';
import { TagData } from '../../types/models';

export enum RequestType {
// eslint-disable-next-line no-unused-vars
PUSH = 'push',
// eslint-disable-next-line no-unused-vars
PULL = 'pull',
}

export enum ActionType {
// eslint-disable-next-line no-unused-vars
COMMIT = 'commit',
// eslint-disable-next-line no-unused-vars
TAG = 'tag',
// eslint-disable-next-line no-unused-vars
BRANCH = 'branch',
}

/**
* Represents a commit.
*/
export interface Commit {
export interface CommitData {
message: string;
committer: string;
committerEmail: string;
tree: string;
parent: string;
author: string;
authorEmail: string;
commitTS?: string; // TODO: Normalize this to commitTimestamp
commitTimestamp?: string;
}

Expand All @@ -21,7 +37,8 @@ export interface Commit {
*/
class Action {
id: string;
type: string;
type: RequestType;
actionType?: ActionType;
method: string;
timestamp: number;
project: string;
Expand All @@ -39,7 +56,7 @@ class Action {
rejected: boolean = false;
autoApproved: boolean = false;
autoRejected: boolean = false;
commitData?: Commit[] = [];
commitData?: CommitData[] = [];
commitFrom?: string;
commitTo?: string;
branch?: string;
Expand All @@ -50,6 +67,8 @@ class Action {
attestation?: string;
lastStep?: Step;
proxyGitPath?: string;
tag?: string;
tagData?: TagData[];
newIdxFiles?: string[];

/**
Expand All @@ -60,7 +79,7 @@ class Action {
* @param {number} timestamp The timestamp of the action
* @param {string} url The URL to the repo that should be proxied (with protocol, origin, repo path, but not the path for the git operation).
*/
constructor(id: string, type: string, method: string, timestamp: number, url: string) {
constructor(id: string, type: RequestType, method: string, timestamp: number, url: string) {
this.id = id;
this.type = type;
this.method = method;
Expand Down
4 changes: 2 additions & 2 deletions src/proxy/actions/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Action } from './Action';
import { Action, RequestType, ActionType } from './Action';
import { Step } from './Step';

export { Action, Step };
export { Action, Step, RequestType, ActionType };
Loading
Loading