From 74305defee0839eb38c4edcd865a65177a006b77 Mon Sep 17 00:00:00 2001 From: ChetanSenta Date: Mon, 10 Aug 2026 17:14:47 +0530 Subject: [PATCH] fix(architecture): make getDirSize async with early-exit-over-limit, exclude .git from size total --- app/api/architecture/route.test.ts | 35 +++++++++++++++++++++++++++++- app/api/architecture/route.ts | 22 ++++++++++++++----- utils/dateHelpers.test.ts | 20 ++++++----------- 3 files changed, 57 insertions(+), 20 deletions(-) diff --git a/app/api/architecture/route.test.ts b/app/api/architecture/route.test.ts index 16b5317f9..a1d51cf72 100644 --- a/app/api/architecture/route.test.ts +++ b/app/api/architecture/route.test.ts @@ -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', () => ({ @@ -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 }); + }); +}); diff --git a/app/api/architecture/route.ts b/app/api/architecture/route.ts index 82d9ae0ab..e0ec7a84c 100644 --- a/app/api/architecture/route.ts +++ b/app/api/architecture/route.ts @@ -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 { 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 @@ -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( diff --git a/utils/dateHelpers.test.ts b/utils/dateHelpers.test.ts index ff4e95114..204ffa19e 100644 --- a/utils/dateHelpers.test.ts +++ b/utils/dateHelpers.test.ts @@ -68,14 +68,12 @@ 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); @@ -83,30 +81,26 @@ describe('dateHelpers', () => { }); 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);