Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated rating tool and extended timeouts #85

Merged
merged 19 commits into from
Dec 4, 2024
Merged
3 changes: 2 additions & 1 deletion backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions backend/src/common/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,13 @@ export interface PackageRating {
BusFactor: number;
ResponsiveMaintainer: number;
LicenseScore: number;
GoodPinningPractice: number;
PullRequest: number;
NetScore: number;
RampUpLatency: number;
CorrectnessLatency: number;
BusFactorLatency: number;
ResponsiveMaintainerLatency: number;
LicenseScoreLatency: number;
GoodPinningPracticeLatency: number;
PullRequestLatency: number;
NetScoreLatency: number;
}
Expand Down Expand Up @@ -92,4 +90,4 @@ export interface PackageTableRow {
JSProgram?: string; // JavaScript program for sensitive modules
standaloneCost?: number; // Standalone cost, excluding dependencies
totalCost?: number; // Total cost including dependencies if applicable
}
}
8 changes: 4 additions & 4 deletions backend/src/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ export async function getScores(token: string, url: string): Promise<metricData>
ResponsiveMaintainer_Latency: -1,
License: -1,
License_Latency: -1,
GoodPinningPractice: -1,
GoodPinningPracticeLatency: -1,
PullRequest: -1,
PullRequestLatency: 0-1
GoodPinningPractice: -1,
GoodPinningPractice_Latency: -1,
PullRequest_Latency: -1
};
console.log('Error calculating score:', error);
return emptyResult;
Expand All @@ -103,4 +103,4 @@ export async function getRepoUrl(url: string): Promise<string> {
export function extractFieldFromPackageJson(packageJson: string, field: string) {
const metadata = JSON.parse(packageJson);
return metadata[field];
}
}
2 changes: 1 addition & 1 deletion backend/src/handlers/PackageRate/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,4 @@ export interface PackageTableRow {
JSProgram?: string; // JavaScript program for sensitive modules
standaloneCost: number; // Standalone cost, excluding dependencies
Rating: PackageRating;
}
}
80 changes: 78 additions & 2 deletions backend/src/services/rate/__tests__/gitAnalysis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const fakeRepoData: repoData = {
licenses: [],
numberOfCommits: -1,
numberOfLines: -1,
pullRequestMetrics: undefined,
documentation: {
hasReadme: false,
numLines: -1,
Expand All @@ -31,7 +32,8 @@ const fakeRepoData: repoData = {
licenses: -1,
numberOfCommits: -1,
numberOfLines: -1,
documentation: -1
documentation: -1,
pullRequests: -1
}
};

Expand Down Expand Up @@ -109,6 +111,80 @@ describe('gitAnalysisClass', () => {
it('should have a gitData with values', async () => {
const result = await gitAnalysisInstance.runTasks(url);
expect(result).not.toBe(fakeRepoData);
}, 10000);
}, 30000);

// In gitAnalysis.test.ts

describe('Pull Request Analysis', () => {
it('should fetch pull request metrics', async () => {
await gitAnalysisInstance.fetchPullRequests(fakeRepoData);
expect(fakeRepoData.pullRequestMetrics).toBeDefined();
if (fakeRepoData.pullRequestMetrics) {
expect(fakeRepoData.pullRequestMetrics.reviewedFraction).toBeGreaterThanOrEqual(0);
expect(fakeRepoData.pullRequestMetrics.reviewedFraction).toBeLessThanOrEqual(1);
expect(fakeRepoData.pullRequestMetrics.totalAdditions).toBeGreaterThanOrEqual(0);
expect(fakeRepoData.pullRequestMetrics.reviewedAdditions).toBeGreaterThanOrEqual(0);
}
}, 30000);

it('should handle repositories with no access or invalid repos', async () => {
const invalidRepo = {
...fakeRepoData,
repoUrl: 'https://github.com/definitely-not-real/non-existent-repo',
repoOwner: 'definitely-not-real',
repoName: 'non-existent-repo'
};

// Temporarily reduce exponential backoff for this test
const originalBackoff = gitAnalysisInstance.exponentialBackoff;
gitAnalysisInstance.exponentialBackoff = async (requestFn) => {
try {
return await requestFn();
} catch (error) {
throw error;
}
};

await gitAnalysisInstance.fetchPullRequests(invalidRepo);

// Restore original exponential backoff
gitAnalysisInstance.exponentialBackoff = originalBackoff;

expect(invalidRepo.pullRequestMetrics).toEqual({
totalAdditions: 0,
reviewedAdditions: 0,
reviewedFraction: 0
});
}, 10000); // Reduced timeout since we're bypassing backoff

it('should handle API errors gracefully', async () => {
const badRepo = {
...fakeRepoData,
repoOwner: '', // Invalid owner
repoName: '' // Invalid name
};

// Temporarily reduce exponential backoff for this test
const originalBackoff = gitAnalysisInstance.exponentialBackoff;
gitAnalysisInstance.exponentialBackoff = async (requestFn) => {
try {
return await requestFn();
} catch (error) {
throw error;
}
};

await gitAnalysisInstance.fetchPullRequests(badRepo);

// Restore original exponential backoff
gitAnalysisInstance.exponentialBackoff = originalBackoff;

expect(badRepo.pullRequestMetrics).toEqual({
totalAdditions: 0,
reviewedAdditions: 0,
reviewedFraction: 0
});
}, 10000); // Reduced timeout since we're bypassing backoff
});

});
98 changes: 94 additions & 4 deletions backend/src/services/rate/__tests__/metricCalc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@ const fakeRepoData: repoData = {
numberOfContributors: 400,
numberOfOpenIssues: 10,
numberOfClosedIssues: 20,
lastCommitDate: "Sat Mar 09 2024",
lastCommitDate: "Sat Nov 09 2024",
licenses: [''],
numberOfCommits: 1200,
numberOfLines: 600,
pullRequestMetrics: {
totalAdditions: 1000,
reviewedAdditions: 800,
reviewedFraction: 0.8
},
documentation: {
hasReadme: true,
numLines: 1000,
Expand All @@ -27,7 +32,8 @@ const fakeRepoData: repoData = {
licenses: 0,
numberOfCommits: 0,
numberOfLines: 0,
documentation: 0
documentation: 0,
pullRequests: 0
}
};

Expand All @@ -43,6 +49,7 @@ const invalidData: repoData = {
licenses: [''],
numberOfCommits: -1,
numberOfLines: -1,
pullRequestMetrics: undefined,
documentation: {
hasReadme: false,
numLines: -1,
Expand All @@ -57,7 +64,8 @@ const invalidData: repoData = {
licenses: -1,
numberOfCommits: -1,
numberOfLines: -1,
documentation: -1
documentation: -1,
pullRequests: -1
}
};

Expand Down Expand Up @@ -241,8 +249,89 @@ describe('metricCalcClass', () => {
expect(netScore).toEqual(0);
});

describe('calculatePinnedDependencies', () => {
it('should return 1.0 for a repo with no dependencies', () => {
fakeRepoData.dependencies = [];
const pinnedDependencies = metricClass.calculatePinnedDependencies(fakeRepoData);
expect(pinnedDependencies).toEqual(1.0);
});

it('should calculate the fraction of pinned dependencies correctly', () => {
fakeRepoData.dependencies = [
{ name: 'dep1', version: '1.0.0' },
{ name: 'dep2', version: '2.3.x' },
{ name: 'dep3', version: '^1.2.3' },
];
const pinnedDependencies = metricClass.calculatePinnedDependencies(fakeRepoData);
expect(pinnedDependencies).toEqual(0.667);
});

it('should return 0 for invalid dependencies', () => {
fakeRepoData.dependencies = undefined;
const pinnedDependencies = metricClass.calculatePinnedDependencies(fakeRepoData);
expect(pinnedDependencies).toEqual(0);
});
});

describe('Pull Request Metrics', () => {
beforeEach(() => {
// Reset fakeRepoData for each test
fakeRepoData.pullRequestMetrics = {
totalAdditions: 1000,
reviewedAdditions: 800,
reviewedFraction: 0.8
};
});

it('should calculate PR score correctly with valid metrics', () => {
const prScore = metricClass.calculatePullRequestScore(fakeRepoData);
expect(prScore).toEqual(0.8);
});

it('should return 0 for undefined PR metrics', () => {
fakeRepoData.pullRequestMetrics = undefined;
const prScore = metricClass.calculatePullRequestScore(fakeRepoData);
expect(prScore).toEqual(0);
});

it('should return 0 for zero total additions', () => {
fakeRepoData.pullRequestMetrics = {
totalAdditions: 0,
reviewedAdditions: 0,
reviewedFraction: 0
};
const prScore = metricClass.calculatePullRequestScore(fakeRepoData);
expect(prScore).toEqual(0);
});

it('should return correct latency value', () => {
fakeRepoData.latency.pullRequests = 2000; // 2 seconds in milliseconds
const latency = metricClass.getPullRequestLatency(fakeRepoData.latency);
expect(latency).toEqual(2.0);
});

it('should include PR score in net score calculation', () => {
// Set up a scenario where we know the expected output
fakeRepoData.licenses = ['MIT'];
fakeRepoData.pullRequestMetrics = {
totalAdditions: 1000,
reviewedAdditions: 1000,
reviewedFraction: 1.0
};

const netScore = metricClass.calculateNetScore(fakeRepoData);
expect(netScore).toBeGreaterThan(0);
expect(netScore).toBeLessThanOrEqual(1);
});
});

// Test the overall getValue function
it('Return correct values from getValue method', () => {
fakeRepoData.dependencies = [
{ name: 'dep1', version: '1.0.0' },
{ name: 'dep2', version: '2.3.x' },
];

const result = metricClass.getValue(fakeRepoData);
expect(result).toHaveProperty('URL', fakeRepoData.repoUrl);
expect(result).toHaveProperty('NetScore');
Expand All @@ -251,5 +340,6 @@ describe('metricCalcClass', () => {
expect(result).toHaveProperty('RampUp');
expect(result).toHaveProperty('ResponsiveMaintainer');
expect(result).toHaveProperty('License');
expect(result).toHaveProperty('GoodPinningPractice');
});
});
});
57 changes: 55 additions & 2 deletions backend/src/services/rate/__tests__/npmAnalysis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ describe('npmAnalysis', () => {
licenses: -1,
numberOfCommits: -1,
numberOfLines: -1,
documentation: -1
documentation: -1,
pullRequests: -1
}
};

Expand Down Expand Up @@ -115,7 +116,8 @@ describe('npmAnalysis', () => {
licenses: -1,
numberOfCommits: -1,
numberOfLines: -1,
documentation: -1
documentation: -1,
pullRequests: -1
}
};

Expand All @@ -141,6 +143,55 @@ describe('npmAnalysis', () => {
expect(mockLogger.logDebug).toHaveBeenCalledWith(`No commits found in the repository ${mockNpmData.repoUrl} in dir ./repo`);
});
});
describe('analyzeDependencies', () => {
it('should calculate pinned dependency fraction correctly', async () => {
const dir = './testRepo'; // Mock directory
const npmData: npmData = {
repoUrl: 'https://github.com/example/repo',
lastCommitDate: '',
documentation: {
hasReadme: true,
numLines: 100,
hasExamples: false,
hasDocumentation: true,
dependencies: undefined, // Initially undefined
},
latency: {
contributors: -1,
openIssues: -1,
closedIssues: -1,
lastCommitDate: -1,
licenses: -1,
numberOfCommits: -1,
numberOfLines: -1,
documentation: -1,
dependencies: -1,
pullRequests: -1,
},
};

// Mock reading of package.json
jest.spyOn(fs, 'readFile').mockResolvedValue(JSON.stringify({
dependencies: {
'dep1': '1.0.0',
'dep2': '2.3.x',
},
devDependencies: {
'dep3': '^1.2.3',
'dep4': '1.2.5',
},
}));

const instance = new npmAnalysis(mockEnvVars);
await instance.analyzeDependencies(dir, npmData);

expect(npmData.documentation.dependencies).toEqual({
total: 4,
pinned: 3,
fractionPinned: 0.75,
});
});
});

describe('deleteRepo', () => {
it('should delete the repository', async () => {
Expand All @@ -160,4 +211,6 @@ describe('npmAnalysis', () => {
expect(mockLogger.logDebug).toHaveBeenCalledWith('Failed to delete repository in ./repo:');
});
});


});
Loading
Loading