mirror of
https://github.com/lukaszraczylo/ghtracker.git
synced 2026-10-12 00:45:38 +00:00
ghtracker authenticates as a GitHub App and scans a configured list of repositories on a schedule. It serves a dashboard, a JSON state endpoint, a health probe and Prometheus metrics. The dashboard lists only items that need attention: failing workflows, pull requests that wait too long or fail their checks, stale issues and projects that failed to scan. It has search, dynamic filters and a button that rescans on demand. The Vue app is embedded in the Go binary. The client protects the GitHub rate limit with conditional requests, a request reserve and Retry-After handling.
180 lines
5.4 KiB
TypeScript
180 lines
5.4 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
activeFilterCount,
|
|
applyFilters,
|
|
emptyFilters,
|
|
facetCounts,
|
|
filtersFromSearch,
|
|
filtersToSearch,
|
|
kindGroup,
|
|
matchesQuery,
|
|
repoMatches,
|
|
type Filters,
|
|
} from './filters'
|
|
import { buildBlocks } from '@/stores/tracker'
|
|
import { alert, repo, state } from '@/test/fixtures'
|
|
|
|
const fleet = () =>
|
|
buildBlocks(
|
|
state({
|
|
repos: [
|
|
repo({ fullName: 'a/one', health: 'crit' }),
|
|
repo({ fullName: 'b/two', health: 'warn', release: null }),
|
|
],
|
|
alerts: [
|
|
alert({
|
|
repo: 'a/one',
|
|
kind: 'workflow_failed',
|
|
severity: 'crit',
|
|
subject: 'CI',
|
|
url: '1',
|
|
}),
|
|
alert({
|
|
repo: 'a/one',
|
|
kind: 'pr_waiting',
|
|
severity: 'warn',
|
|
subject: '#3 Bump grpc',
|
|
url: '2',
|
|
}),
|
|
alert({
|
|
repo: 'b/two',
|
|
kind: 'issues_stale',
|
|
severity: 'warn',
|
|
subject: '4 stale issues',
|
|
url: '3',
|
|
}),
|
|
alert({
|
|
repo: 'b/two',
|
|
kind: 'partial_data',
|
|
severity: 'warn',
|
|
subject: 'Incomplete data',
|
|
url: '4',
|
|
}),
|
|
],
|
|
}),
|
|
)
|
|
|
|
const withFilters = (over: Partial<Filters>): Filters => ({ ...emptyFilters(), ...over })
|
|
|
|
describe('kindGroup', () => {
|
|
it.each([
|
|
['workflow_failed', 'workflow'],
|
|
['workflow_stuck', 'workflow'],
|
|
['pr_waiting', 'pr'],
|
|
['pr_checks_failing', 'pr'],
|
|
['issues_stale', 'issue'],
|
|
['refresh_failed', 'data'],
|
|
['data_stale', 'data'],
|
|
['partial_data', 'data'],
|
|
['something_new', 'data'],
|
|
])('%s -> %s', (kind, group) => expect(kindGroup(kind)).toBe(group))
|
|
})
|
|
|
|
describe('matchesQuery', () => {
|
|
it('requires every term, ignores case and accepts an empty query', () => {
|
|
expect(matchesQuery('', 'anything')).toBe(true)
|
|
expect(matchesQuery(' ', 'anything')).toBe(true)
|
|
expect(matchesQuery('bump GRPC', 'o/r', '#3 Bump google.golang.org/grpc')).toBe(true)
|
|
expect(matchesQuery('bump nothing', '#3 Bump grpc')).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('applyFilters', () => {
|
|
it('returns everything for empty filters', () => {
|
|
expect(applyFilters(fleet(), emptyFilters()).flatMap((b) => b.rows)).toHaveLength(4)
|
|
})
|
|
|
|
it('combines severity, kind, owner and search', () => {
|
|
const rows = (f: Filters) =>
|
|
applyFilters(fleet(), f).flatMap((b) => b.rows.map((r) => r.subject))
|
|
expect(rows(withFilters({ severity: 'crit' }))).toEqual(['CI'])
|
|
expect(rows(withFilters({ kinds: ['pr', 'issue'] }))).toEqual([
|
|
'#3 Bump grpc',
|
|
'4 stale issues',
|
|
])
|
|
expect(rows(withFilters({ owners: ['b'] }))).toEqual(['4 stale issues', 'Incomplete data'])
|
|
expect(rows(withFilters({ query: 'grpc' }))).toEqual(['#3 Bump grpc'])
|
|
expect(rows(withFilters({ query: 'a/one', kinds: ['pr'] }))).toEqual(['#3 Bump grpc'])
|
|
expect(rows(withFilters({ repo: 'b/two', severity: 'crit' }))).toEqual([])
|
|
})
|
|
|
|
it('drops blocks left without rows', () => {
|
|
expect(
|
|
applyFilters(fleet(), withFilters({ kinds: ['workflow'] })).map((b) => b.repo.fullName),
|
|
).toEqual(['a/one'])
|
|
})
|
|
|
|
it('also searches the release tag', () => {
|
|
expect(
|
|
applyFilters(fleet(), withFilters({ query: 'v1.0.0' })).map((b) => b.repo.fullName),
|
|
).toEqual(['a/one'])
|
|
})
|
|
})
|
|
|
|
describe('facetCounts', () => {
|
|
it('counts each facet with the others applied, but not itself', () => {
|
|
const f = withFilters({ kinds: ['pr'] })
|
|
const c = facetCounts(fleet(), f, ['a', 'b'])
|
|
expect(c.kinds).toEqual({ workflow: 1, pr: 1, issue: 1, data: 1 })
|
|
expect(c.severity).toEqual({ all: 1, crit: 0, warn: 1 })
|
|
expect(c.owners).toEqual({ a: 1, b: 0 })
|
|
})
|
|
|
|
it('counts everything for empty filters', () => {
|
|
const c = facetCounts(fleet(), emptyFilters(), ['a', 'b'])
|
|
expect(c.severity).toEqual({ all: 4, crit: 1, warn: 3 })
|
|
expect(c.owners).toEqual({ a: 2, b: 2 })
|
|
})
|
|
})
|
|
|
|
describe('repoMatches', () => {
|
|
it('uses the query and owner filters only', () => {
|
|
const r = repo({ fullName: 'a/one', description: 'Kubernetes mirror' })
|
|
expect(repoMatches(r, withFilters({ query: 'mirror' }))).toBe(true)
|
|
expect(repoMatches(r, withFilters({ query: 'nope' }))).toBe(false)
|
|
expect(repoMatches(r, withFilters({ owners: ['b'] }))).toBe(false)
|
|
expect(repoMatches(r, withFilters({ severity: 'crit', kinds: ['pr'] }))).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('activeFilterCount', () => {
|
|
it('counts each active filter value', () => {
|
|
expect(activeFilterCount(emptyFilters())).toBe(0)
|
|
expect(
|
|
activeFilterCount(
|
|
withFilters({
|
|
query: ' x ',
|
|
severity: 'warn',
|
|
kinds: ['pr', 'issue'],
|
|
owners: ['a'],
|
|
repo: 'a/b',
|
|
}),
|
|
),
|
|
).toBe(6)
|
|
expect(activeFilterCount(withFilters({ query: ' ' }))).toBe(0)
|
|
})
|
|
})
|
|
|
|
describe('URL round trip', () => {
|
|
it('omits defaults', () => {
|
|
expect(filtersToSearch(emptyFilters())).toBe('')
|
|
})
|
|
|
|
it('serialises and restores every filter', () => {
|
|
const f = withFilters({
|
|
query: 'bump grpc',
|
|
severity: 'crit',
|
|
kinds: ['pr', 'data'],
|
|
owners: ['a', 'b'],
|
|
repo: 'a/one',
|
|
})
|
|
expect(filtersFromSearch(filtersToSearch(f))).toEqual(f)
|
|
})
|
|
|
|
it('ignores unknown values from a hand-edited URL', () => {
|
|
expect(filtersFromSearch('?sev=nope&kind=pr,bogus&owner=,a')).toEqual(
|
|
withFilters({ kinds: ['pr'], owners: ['a'] }),
|
|
)
|
|
})
|
|
})
|