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
4 changes: 2 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
},
"parser": "espree",
"env": {
"cypress/globals": true
"node": true,
"mocha": true
},
"plugins": ["cypress"],
"rules": {
"@typescript-eslint/no-unused-expressions": "off"
}
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ jobs:
npm run test-coverage-ci
npm run test-coverage-ci --workspaces --if-present

- name: Test MongoDB Integration
run: npm run test:mongo
env:
GIT_PROXY_MONGO_CONNECTION_STRING: mongodb://localhost:27017/git-proxy-test

- name: Upload test coverage report
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
with:
Expand Down
21 changes: 21 additions & 0 deletions docker-compose.mongo-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
version: '3.8'

services:
mongodb-test:
image: mongo:4.4
container_name: git-proxy-mongo-test
ports:
- '27017:27017'
environment:
MONGO_INITDB_DATABASE: git-proxy-test
volumes:
- mongo-test-data:/data/db
command: mongod --bind_ip_all
healthcheck:
test: ['CMD', 'mongosh', '--eval', "db.runCommand('ping')"]
interval: 10s
timeout: 5s
retries: 5

volumes:
mongo-test-data:
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"gen-schema-doc": "node ./scripts/doc-schema.js",
"cypress:run": "cypress run",
"cypress:open": "cypress open",
"test:mongo": "node test/db/mongo/run-mongo-tests.js",
"test:mongo:docker": "node test/db/mongo/run-mongo-tests-docker.js",
"generate-config-types": "quicktype --src-lang schema --lang typescript --out src/config/generated/config.ts --top-level GitProxyConfig config.schema.json && ts-node scripts/add-banner.ts src/config/generated/config.ts && prettier --write src/config/generated/config.ts"
},
"bin": {
Expand Down
9 changes: 7 additions & 2 deletions src/db/mongo/repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,13 @@ export const getRepoByUrl = async (repoUrl: string): Promise<Repo | null> => {

export const getRepoById = async (_id: string): Promise<Repo | null> => {
const collection = await connect(collectionName);
const doc = await collection.findOne({ _id: new ObjectId(_id) });
return doc ? toClass(doc, Repo.prototype) : null;
try {
const doc = await collection.findOne({ _id: new ObjectId(_id) });
return doc ? toClass(doc, Repo.prototype) : null;
} catch (error) {
// Handle invalid ObjectId strings gracefully
return null;
}
};

export const createRepo = async (repo: Repo): Promise<Repo> => {
Expand Down
281 changes: 281 additions & 0 deletions test/db/database-comparison.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
const { expect } = require('chai');
const { MongoClient } = require('mongodb');
const fs = require('fs');
const path = require('path');

// Import both database implementations
const fileRepo = require('../../src/db/file/repo');
const fileUsers = require('../../src/db/file/users');
const mongoRepo = require('../../src/db/mongo/repo');
const mongoUsers = require('../../src/db/mongo/users');

describe('Database Comparison Tests', () => {
let mongoClient;
let mongoDb;
let fileDbPath;

before(async () => {
// Setup MongoDB connection
const connectionString =
process.env.GIT_PROXY_MONGO_CONNECTION_STRING || 'mongodb://localhost:27017/git-proxy-test';
mongoClient = new MongoClient(connectionString);
await mongoClient.connect();
mongoDb = mongoClient.db('git-proxy-test');

// Setup file database path
fileDbPath = path.join(__dirname, '../../test-data');
if (!fs.existsSync(fileDbPath)) {
fs.mkdirSync(fileDbPath, { recursive: true });
}
});

after(async () => {
if (mongoClient) {
await mongoClient.close();
}

// Clean up file database
if (fs.existsSync(fileDbPath)) {
fs.rmSync(fileDbPath, { recursive: true, force: true });
}
});

beforeEach(async () => {
// Clean MongoDB collections
await mongoDb.collection('repos').deleteMany({});
await mongoDb.collection('users').deleteMany({});

// Clean file database
const reposPath = path.join(fileDbPath, 'repos');
const usersPath = path.join(fileDbPath, 'users');

if (fs.existsSync(reposPath)) {
fs.rmSync(reposPath, { recursive: true, force: true });
}
if (fs.existsSync(usersPath)) {
fs.rmSync(usersPath, { recursive: true, force: true });
}

// Ensure directories exist
fs.mkdirSync(reposPath, { recursive: true });
fs.mkdirSync(usersPath, { recursive: true });

// Clear module cache to ensure fresh instances
delete require.cache[require.resolve('../../src/db/file/repo')];
delete require.cache[require.resolve('../../src/db/file/users')];
delete require.cache[require.resolve('../../src/db/mongo/repo')];
delete require.cache[require.resolve('../../src/db/mongo/users')];
});

describe('Repository Operations Comparison', () => {
it('should create repositories with same behavior', async () => {
const repoData = {
name: 'test-repo',
url: 'https://github.com/test/test-repo',
canPush: ['user1'],
canAuthorise: ['user2'],
};

// Create in both databases
const fileResult = await fileRepo.createRepo(repoData);
const mongoResult = await mongoRepo.createRepo(repoData);

// Both should return success
expect(fileResult).to.have.property('insertedId');
expect(mongoResult).to.have.property('insertedId');

// Both should be retrievable
const fileRepoData = await fileRepo.getRepo('test-repo');
const mongoRepoData = await mongoRepo.getRepo('test-repo');

expect(fileRepoData).to.have.property('name', 'test-repo');
expect(mongoRepoData).to.have.property('name', 'test-repo');
expect(fileRepoData).to.have.property('url', 'https://github.com/test/test-repo');
expect(mongoRepoData).to.have.property('url', 'https://github.com/test/test-repo');
});

it('should get repositories by URL with same behavior', async () => {
const repoData = {
name: 'test-repo',
url: 'https://github.com/test/test-repo',
canPush: ['user1'],
canAuthorise: ['user2'],
};

await fileRepo.createRepo(repoData);
await mongoRepo.createRepo(repoData);

const fileRepoData = await fileRepo.getRepoByUrl('https://github.com/test/test-repo');
const mongoRepoData = await mongoRepo.getRepoByUrl('https://github.com/test/test-repo');

expect(fileRepoData).to.have.property('name', 'test-repo');
expect(mongoRepoData).to.have.property('name', 'test-repo');
});

it('should add users to canPush with same behavior', async () => {
const repoData = {
name: 'test-repo',
url: 'https://github.com/test/test-repo',
canPush: ['user1'],
canAuthorise: ['user2'],
};

await fileRepo.createRepo(repoData);
await mongoRepo.createRepo(repoData);

await fileRepo.addUserCanPush('test-repo', 'newuser');
await mongoRepo.addUserCanPush('test-repo', 'newuser');

const fileRepoData = await fileRepo.getRepo('test-repo');
const mongoRepoData = await mongoRepo.getRepo('test-repo');

expect(fileRepoData.canPush).to.include('newuser');
expect(mongoRepoData.canPush).to.include('newuser');
});

it('should delete repositories with same behavior', async () => {
const repoData = {
name: 'test-repo',
url: 'https://github.com/test/test-repo',
canPush: ['user1'],
canAuthorise: ['user2'],
};

await fileRepo.createRepo(repoData);
await mongoRepo.createRepo(repoData);

await fileRepo.deleteRepo('test-repo');
await mongoRepo.deleteRepo('test-repo');

const fileRepoData = await fileRepo.getRepo('test-repo');
const mongoRepoData = await mongoRepo.getRepo('test-repo');

expect(fileRepoData).to.be.null;
expect(mongoRepoData).to.be.null;
});
});

describe('User Operations Comparison', () => {
it('should create users with same behavior', async () => {
const userData = {
username: 'testuser',
email: '[email protected]',
gitAccount: 'testaccount',
admin: false,
};

const fileResult = await fileUsers.createUser(userData);
const mongoResult = await mongoUsers.createUser(userData);

expect(fileResult).to.have.property('insertedId');
expect(mongoResult).to.have.property('insertedId');

const fileUser = await fileUsers.findUser('testuser');
const mongoUser = await mongoUsers.findUser('testuser');

expect(fileUser).to.have.property('username', 'testuser');
expect(mongoUser).to.have.property('username', 'testuser');
expect(fileUser).to.have.property('email', '[email protected]');
expect(mongoUser).to.have.property('email', '[email protected]');
});

it('should find users by email with same behavior', async () => {
const userData = {
username: 'testuser',
email: '[email protected]',
gitAccount: 'testaccount',
admin: false,
};

await fileUsers.createUser(userData);
await mongoUsers.createUser(userData);

const fileUser = await fileUsers.findUserByEmail('[email protected]');
const mongoUser = await mongoUsers.findUserByEmail('[email protected]');

expect(fileUser).to.have.property('username', 'testuser');
expect(mongoUser).to.have.property('username', 'testuser');
});

it('should update users with same behavior', async () => {
const userData = {
username: 'testuser',
email: '[email protected]',
gitAccount: 'testaccount',
admin: false,
};

const fileResult = await fileUsers.createUser(userData);
const mongoResult = await mongoUsers.createUser(userData);

const fileUserId = fileResult.insertedId.toString();
const mongoUserId = mongoResult.insertedId.toString();

await fileUsers.updateUser(fileUserId, { email: '[email protected]', admin: true });
await mongoUsers.updateUser(mongoUserId, { email: '[email protected]', admin: true });

const fileUser = await fileUsers.findUser('testuser');
const mongoUser = await mongoUsers.findUser('testuser');

expect(fileUser).to.have.property('email', '[email protected]');
expect(mongoUser).to.have.property('email', '[email protected]');
expect(fileUser).to.have.property('admin', true);
expect(mongoUser).to.have.property('admin', true);
});

it('should delete users with same behavior', async () => {
const userData = {
username: 'testuser',
email: '[email protected]',
gitAccount: 'testaccount',
admin: false,
};

const fileResult = await fileUsers.createUser(userData);
const mongoResult = await mongoUsers.createUser(userData);

const fileUserId = fileResult.insertedId.toString();
const mongoUserId = mongoResult.insertedId.toString();

await fileUsers.deleteUser(fileUserId);
await mongoUsers.deleteUser(mongoUserId);

const fileUser = await fileUsers.findUser('testuser');
const mongoUser = await mongoUsers.findUser('testuser');

expect(fileUser).to.be.null;
expect(mongoUser).to.be.null;
});
});

describe('Error Handling Comparison', () => {
it('should handle non-existent repositories consistently', async () => {
const fileRepo = await fileRepo.getRepo('non-existent');
const mongoRepo = await mongoRepo.getRepo('non-existent');

expect(fileRepo).to.be.null;
expect(mongoRepo).to.be.null;
});

it('should handle non-existent users consistently', async () => {
const fileUser = await fileUsers.findUser('non-existent');
const mongoUser = await mongoUsers.findUser('non-existent');

expect(fileUser).to.be.null;
expect(mongoUser).to.be.null;
});

it('should handle invalid operations consistently', async () => {
// Try to add user to non-existent repo
await fileRepo.addUserCanPush('non-existent', 'user1');
await mongoRepo.addUserCanPush('non-existent', 'user1');

// Both should not throw errors (graceful handling)
const fileRepo = await fileRepo.getRepo('non-existent');
const mongoRepo = await mongoRepo.getRepo('non-existent');

expect(fileRepo).to.be.null;
expect(mongoRepo).to.be.null;
});
});
});
Loading
Loading