/**
* The /repos landing page must git-walk only the repos it renders.
*
* The page renders PAGE_SIZE cards but sorted on `lastCommit.timestamp`,
* which only exists AFTER enrichRepos() has walked a repo's full history.
* So picking the top 25 meant enriching all of them first and discarding
* the rest: on the live backend, 147 repos walked to show 25, which cold
* (page cache evicted) measured 28.3s of `git log --all`.
*
* `computes` counts real git passes, so this asserts the scope of the work
* mechanically instead of inferring it from wall-clock timing.
*/
import test from 'brittle'
import { mkdtemp, mkdir, writeFile } from '#node:fs/promises'
import { tmpdir } from '#node:os'
import { join } from '#node:path'
import { execFileSync } from '../lib/_compat-exec.js'
import process from '#node:process'
import { syncHandlers } from '../lib/services/sync.js'
import { renderRepoPage } from '../lib/web/pages/repos.js'
import { getRepoActivityStats, resetRepoActivityCacheForTests } from '../lib/services/repo-activity.js'
function git (cwd, args) {
return execFileSync('git', args, {
cwd,
stdio: ['ignore', 'pipe', 'ignore'],
env: {
...process.env,
GIT_AUTHOR_NAME: 't',
GIT_AUTHOR_EMAIL: 't@t',
GIT_COMMITTER_NAME: 't',
GIT_COMMITTER_EMAIL: 't@t'
}
}).toString()
}
async function makeBareRepo (root, name, n) {
const barePath = join(root, name + '.git')
const workPath = join(root, name + '-work')
await mkdir(barePath, { recursive: true })
await mkdir(workPath, { recursive: true })
git(barePath, ['init', '--bare', '-q'])
git(workPath, ['init', '-q'])
for (let i = 0; i < n; i++) {
await writeFile(join(workPath, 'f'), 'v' + i)
git(workPath, ['add', 'f'])
git(workPath, ['commit', '-q', '-m', 'c' + i])
}
const branch = git(workPath, ['rev-parse', '--abbrev-ref', 'HEAD']).trim()
git(workPath, ['remote', 'add', 'origin', barePath])
git(workPath, ['push', '-q', 'origin', branch])
return barePath
}
/**
* Minimal svc wired to the REAL sync RPC handlers and the REAL page code,
* so this exercises the actual dispatch path rather than a mock of it.
*/
function makeSvc (root, names) {
const repos = new Map()
for (const name of names) repos.set(name, { path: join(root, name + '.git') })
const svc = {
_sync: {
repos,
status () {
return {
repos: names.map((name, i) => ({
name,
serving: true,
branches: 1,
// Distinct, descending so ordering is deterministic and the
// first page is a known subset.
lastUpdated: 1_700_000_000_000 - i * 60_000
})),
serving: names.length,
serverKey: 'k'.repeat(64)
}
}
}
}
const handlers = syncHandlers(svc)
svc.dispatch = async (name, params = {}, ctx = {}) => {
const h = handlers.find((x) => x.name === name)
if (!h) throw new Error('no handler: ' + name)
return h.fn(ctx, params)
}
return svc
}
test('/repos page enriches only the repos it renders', async (t) => {
const root = await mkdtemp(join(tmpdir(), 'repos-scope-'))
const names = ['a', 'b', 'c', 'd', 'e']
for (const n of names) await makeBareRepo(root, n, 2)
const svc = makeSvc(root, names)
resetRepoActivityCacheForTests()
const page = await renderRepoPage(svc, {}, { offset: 0, limit: 2 })
// Guard: loadRepoList swallows a dispatch failure and falls back to the
// unenriched status, which would show 0 computes and pass this test for
// entirely the wrong reason. Prove real cards were rendered first.
t.ok(page.html && page.html.includes('repo-card'), 'rendered real cards')
t.is(page.count, 2, 'rendered exactly one page of 2')
t.is(page.total, names.length, 'knows the full total without walking it')
const { computes } = getRepoActivityStats()
t.is(computes, 2, 'git-walked ONLY the 2 rendered repos (was: all ' + names.length + ')')
})