Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
35 changes: 34 additions & 1 deletion app/api/architecture/route.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { expect, test, vi } from 'vitest';
import { expect, test, vi, describe, it } from 'vitest';
import { POST } from './route';
import { NextRequest } from 'next/server';
import fs from 'fs';
import path from 'path';
import os from 'os';

// Note: Ensure getDirSize is exported in route.ts if you plan to test it directly.
// You can also mock dependencies if needed.
import { getDirSize } from './route'; // Assumes you optionally add `export` to `getDirSize` for the test.

// Mock next-auth session to simulate a logged-in user
vi.mock('@/auth', () => ({
Expand All @@ -23,3 +30,29 @@ test('POST returns 400 if repoUrl is missing', async () => {
const data = await res.json();
expect(data.error).toBe('Repository URL is required');
});

describe('[Bug fix] getDirSize β€” async and early-exit behavior', () => {
it('excludes .git directory contents from the size total', async () => {
// Set up a temp dir with a small working tree and a large .git folder.
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'test-dirsize-'));
fs.writeFileSync(path.join(tmpDir, 'small.txt'), 'a'.repeat(100));
fs.mkdirSync(path.join(tmpDir, '.git'));
fs.writeFileSync(path.join(tmpDir, '.git', 'large-object'), 'b'.repeat(10_000));

const size = await getDirSize(tmpDir);
expect(size).toBe(100); // .git contents excluded

fs.rmSync(tmpDir, { recursive: true, force: true });
});

it('stops walking once the running total exceeds the given limit', async () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'test-dirsize-limit-'));
fs.writeFileSync(path.join(tmpDir, 'big.txt'), 'x'.repeat(1000));
fs.writeFileSync(path.join(tmpDir, 'also-big.txt'), 'y'.repeat(1000));

const size = await getDirSize(tmpDir, 500); // limit lower than either file alone
expect(size).toBeGreaterThan(500);

fs.rmSync(tmpDir, { recursive: true, force: true });
});
});
22 changes: 16 additions & 6 deletions app/api/architecture/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,26 @@ function decrementClones(ip: string): void {
/**
* Measures total size of a directory recursively.
*/
function getDirSize(dirPath: string): number {
export async function getDirSize(dirPath: string, limit = Infinity): Promise<number> {
let totalSize = 0;
try {
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
for (const entry of entries) {
// Excluded from the analysis anyway (see IGNORED_DIRS below) β€”
// no reason to count full git history against the size limit.
if (entry.isDirectory() && entry.name === '.git') continue;

const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
totalSize += getDirSize(fullPath);
totalSize += await getDirSize(fullPath, limit - totalSize);
} else if (entry.isFile()) {
const stats = fs.statSync(fullPath);
const stats = await fs.promises.stat(fullPath);
totalSize += stats.size;
}
// Early exit: once we're already over the limit, there's no need
// to keep walking the rest of the tree β€” the caller only needs
// to know "is this over the limit", not the exact total.
if (totalSize > limit) return totalSize;
}
} catch {
// Ignore errors during size calculation
Expand Down Expand Up @@ -361,8 +369,10 @@ export async function POST(req: NextRequest) {
);
}

// Check disk quota - if clone is too large, abort and clean up
const dirSize = getDirSize(tempDir);
// Check disk quota - if clone is too large, abort and clean up.
// getDirSize is async and exits early once over the limit, so this
// doesn't block the event loop for the full walk on an oversized repo.
const dirSize = await getDirSize(tempDir, MAX_TEMP_DIR_SIZE_BYTES);
if (dirSize > MAX_TEMP_DIR_SIZE_BYTES) {
fs.rmSync(tempDir, { recursive: true, force: true });
return NextResponse.json(
Expand Down
20 changes: 7 additions & 13 deletions utils/dateHelpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,45 +68,39 @@ describe('dateHelpers', () => {
});

it('returns zero metrics for an array containing only Invalid Date strings', () => {
// Removed Z here
const result = processCommitTimestamps(['2024-13-99T25:99:00', 'hello world']);
const result = processCommitTimestamps(['2024-13-99T25:99:00Z', 'hello world']);
expect(result).toEqual({ morning: 0, afternoon: 0, evening: 0, night: 0 });
});

it('counts valid morning commits correctly', () => {
// Removed Z from both strings
const result = processCommitTimestamps(['2024-03-10T09:00:00', '2024-03-10T11:30:00']);
const result = processCommitTimestamps(['2024-03-10T09:00:00Z', '2024-03-10T11:30:00Z']);
expect(result.morning).toBe(2);
expect(result.afternoon).toBe(0);
expect(result.evening).toBe(0);
expect(result.night).toBe(0);
});

it('counts valid afternoon commits correctly', () => {
// Removed Z from both strings
const result = processCommitTimestamps(['2024-03-10T12:00:00', '2024-03-10T17:59:00']);
const result = processCommitTimestamps(['2024-03-10T12:00:00Z', '2024-03-10T17:59:00Z']);
expect(result.morning).toBe(0);
expect(result.afternoon).toBe(2);
});

it('counts valid evening commits correctly', () => {
// Removed Z from both strings
const result = processCommitTimestamps(['2024-03-10T18:00:00', '2024-03-10T23:59:00']);
const result = processCommitTimestamps(['2024-03-10T18:00:00Z', '2024-03-10T23:59:00Z']);
expect(result.evening).toBe(2);
});

it('counts valid night commits correctly', () => {
// Removed Z from both strings
const result = processCommitTimestamps(['2024-03-10T00:00:00', '2024-03-10T05:59:00']);
const result = processCommitTimestamps(['2024-03-10T00:00:00Z', '2024-03-10T05:59:00Z']);
expect(result.night).toBe(2);
});

it('ignores invalid dates while counting valid ones', () => {
// Removed Z from strings
const result = processCommitTimestamps([
'2024-03-10T09:00:00',
'2024-03-10T09:00:00Z',
'invalid-date',
'2024-03-10T14:00:00',
'2024-03-10T14:00:00Z',
]);
expect(result.morning).toBe(1);
expect(result.afternoon).toBe(1);
Expand Down
Loading