Files
simstudioai--sim/apps/sim/tools/github/status_check_rollup.test.ts
WeHub Mirror 6bf8bebf51
CI / Test and Build (push) Failing after 1s
CI / Migrate Dev DB (push) Has been skipped
CI / Migrate DB (push) Has been skipped
CodeQL / Analyze actions (push) Has been cancelled
CodeQL / Analyze javascript-typescript (push) Has been cancelled
CI / Detect Version (push) Has been cancelled
CI / Detect Desktop Changes (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/cron.Dockerfile, ubuntu-latest, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build AMD64 (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/cron.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/db.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/pii.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/realtime.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-8vcpu-ubuntu-2404-arm, ./docker/app.Dockerfile, linux-arm64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
Helm Chart / Lint, test, and validate chart (push) Has been cancelled
Helm Chart / Chart version bumped (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Build Dev ECR (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core) (push) Has been cancelled
CI / Promote Images (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Check Desktop Signing Secrets (push) Has been cancelled
CI / Desktop Release (push) Has been cancelled
CI / Create Desktop Prerelease (push) Has been cancelled
CI / Desktop Prerelease Build (push) Has been cancelled
CI / Publish Desktop Prerelease (push) Has been cancelled
CI / Prune Desktop Prereleases (push) Has been cancelled
Helm Chart / Install on kind and run helm test (push) Has been cancelled
WeHub snapshot of cb28d14c6f2c081de7a0d8729a8c816c9adef67a
2026-08-10 11:17:50 +08:00

237 lines
7.4 KiB
TypeScript

/**
* @vitest-environment node
*/
import { describe, expect, it } from 'vitest'
import { statusCheckRollupTool } from '@/tools/github/status_check_rollup'
import type { StatusCheckRollupParams } from '@/tools/github/types'
const SHA = 'a'.repeat(40)
const BASE_PARAMS: StatusCheckRollupParams = {
owner: 'octo',
repo: 'demo',
sha: SHA,
pullNumber: 7,
apiKey: 'ghp_test',
}
function rollupPayload(nodes: unknown[], overrides: Record<string, unknown> = {}) {
return {
data: {
repository: {
object: {
__typename: 'Commit',
statusCheckRollup: {
state: 'FAILURE',
contexts: {
totalCount: nodes.length,
pageInfo: { hasNextPage: false, endCursor: null },
nodes,
...overrides,
},
},
},
},
},
}
}
/**
* Shaped after a real `statusCheckRollup` node captured from api.github.com.
* GraphQL puts `title`/`summary` flat on `CheckRun` — there is no nested
* `output` object, which is REST's shape — and GitHub Actions leaves both null.
*/
function actionsCheckRun(overrides: Record<string, unknown> = {}) {
return {
__typename: 'CheckRun',
name: 'Test and Build / Lint and Test',
status: 'COMPLETED',
conclusion: 'FAILURE',
detailsUrl: 'https://github.com/octo/demo/actions/runs/30151931961/job/89663652943',
databaseId: 89663652943,
isRequired: true,
title: null,
summary: null,
...overrides,
}
}
function statusContext(overrides: Record<string, unknown> = {}) {
return {
__typename: 'StatusContext',
context: 'Vercel',
state: 'SUCCESS',
description: 'Skipped - Not affected',
targetUrl: 'https://vercel.com/octo/demo/2ZjPVEUCzk5uRDsnHBh8aXVP9XNm',
isRequired: false,
...overrides,
}
}
async function parse(payload: unknown, params: StatusCheckRollupParams = BASE_PARAMS) {
return statusCheckRollupTool.transformResponse!(Response.json(payload), params)
}
describe('github_status_check_rollup', () => {
it('pins the read to a commit SHA and to the pull request that decides requiredness', () => {
const body = statusCheckRollupTool.request.body!({ ...BASE_PARAMS, cursor: 'CURSOR_1' }) as {
query: string
variables: Record<string, unknown>
}
expect(body.query).toContain('object(oid: $sha)')
expect(body.query).toContain('isRequired(pullRequestNumber: $number)')
expect(body.query).toContain('contexts(first: 100, after: $cursor)')
// GraphQL's CheckRun has flat `title`/`summary`; selecting REST's nested
// `output { ... }` is rejected with "Field 'output' doesn't exist on type
// 'CheckRun'" — as an HTTP 200 errors payload, so no fixture would catch it.
expect(body.query).not.toContain('output {')
expect(body.variables).toEqual({
owner: 'octo',
repo: 'demo',
sha: SHA,
number: 7,
cursor: 'CURSOR_1',
})
})
it('parses an Actions check run, whose reported output is always null', async () => {
const result = await parse(rollupPayload([actionsCheckRun()]))
expect(result.success).toBe(true)
expect(result.output.contexts).toEqual([
{
__typename: 'CheckRun',
name: 'Test and Build / Lint and Test',
status: 'COMPLETED',
conclusion: 'FAILURE',
detailsUrl: 'https://github.com/octo/demo/actions/runs/30151931961/job/89663652943',
// The job id in detailsUrl is databaseId, which is what github_job_logs takes.
databaseId: 89663652943,
isRequired: true,
title: null,
summary: null,
},
])
})
it('parses every legitimately-null field rather than demanding a string', async () => {
const result = await parse(
rollupPayload([
actionsCheckRun({
status: 'QUEUED',
conclusion: null,
detailsUrl: null,
databaseId: null,
}),
statusContext({ description: null, targetUrl: null }),
])
)
expect(result.output.contexts[0]).toMatchObject({
conclusion: null,
detailsUrl: null,
databaseId: null,
})
expect(result.output.contexts[1]).toMatchObject({ description: null, targetUrl: null })
})
it('fails on a missing requiredness signal rather than reading it as optional', async () => {
// GraphQL declares isRequired non-null on both variants, so an absent value
// means something changed — and defaulting it would quietly let a failing
// required check stop blocking the green verdict.
await expect(parse(rollupPayload([actionsCheckRun({ isRequired: null })]))).rejects.toThrow(
/isRequired must be a boolean/
)
})
it('keeps a third-party app output that is actually populated', async () => {
const result = await parse(
rollupPayload([
actionsCheckRun({
name: 'codecov/patch',
title: '80% of diff hit',
summary: 'Coverage dropped',
}),
])
)
expect(result.output.contexts[0]).toMatchObject({
title: '80% of diff hit',
summary: 'Coverage dropped',
})
})
it('returns check runs and legacy statuses from the one merged read', async () => {
// The merge is the reason this tool exists: Actions reports as check runs
// while providers like Vercel still post only legacy commit statuses, and a
// reader of either source alone would miss the other.
const result = await parse(rollupPayload([actionsCheckRun(), statusContext()]))
expect(result.output.contexts.map((entry) => entry.__typename)).toEqual([
'CheckRun',
'StatusContext',
])
expect(result.output.contexts[1]).toEqual({
__typename: 'StatusContext',
context: 'Vercel',
state: 'SUCCESS',
description: 'Skipped - Not affected',
targetUrl: 'https://vercel.com/octo/demo/2ZjPVEUCzk5uRDsnHBh8aXVP9XNm',
isRequired: false,
})
})
it('carries the pagination signals a caller needs to detect truncation', async () => {
const result = await parse(
rollupPayload([actionsCheckRun()], {
totalCount: 31,
pageInfo: { hasNextPage: true, endCursor: 'CURSOR_2' },
})
)
expect(result.output).toMatchObject({
state: 'FAILURE',
totalCount: 31,
hasNextPage: true,
endCursor: 'CURSOR_2',
})
expect(result.output.contexts).toHaveLength(1)
})
it('reports a commit with no checks as a null state and no contexts', async () => {
const result = await parse({
data: { repository: { object: { __typename: 'Commit', statusCheckRollup: null } } },
})
expect(result.output).toEqual({
state: null,
totalCount: 0,
hasNextPage: false,
endCursor: null,
contexts: [],
})
})
it('fails rather than reporting no checks when the commit is unknown', async () => {
await expect(parse({ data: { repository: { object: null } } })).rejects.toThrow(
new RegExp(`Commit ${SHA} was not found`)
)
})
it('fails on a GraphQL error payload delivered with HTTP 200', async () => {
await expect(
parse({
data: { repository: null },
errors: [{ message: 'Resource not accessible by integration' }],
})
).rejects.toThrow(/Resource not accessible by integration/)
})
it('stops on a context type it does not know how to read', async () => {
await expect(parse(rollupPayload([{ __typename: 'FutureCheckKind' }]))).rejects.toThrow(
/unsupported type "FutureCheckKind"/
)
})
})