Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
76 changes: 76 additions & 0 deletions __tests__/commentManager.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { previousCommentFor, generateComment } from '../src/commentManager'
import type { DurationReport } from '../src/types'

describe('previousCommentFor', () => {
const validComment = {
user: { login: 'github-actions[bot]', type: 'Bot' },
body: '🕒 Workflow "Test Workflow" took 60s which is a decrease with 10s (14.29%) compared to latest run on master/main.'
}

it('returns true for a matching GitHub Actions bot comment', () => {
const matcher = previousCommentFor('Test Workflow')
expect(matcher(validComment)).toBe(true)
})

it('returns false if login is incorrect', () => {
const matcher = previousCommentFor('Test Workflow')
const comment = {
...validComment,
user: { login: 'someone-else', type: 'Bot' }
}
expect(matcher(comment)).toBe(false)
})

it('returns false if type is not Bot', () => {
const matcher = previousCommentFor('Test Workflow')
const comment = {
...validComment,
user: { login: 'github-actions[bot]', type: 'User' }
}
expect(matcher(comment)).toBe(false)
})

it('returns false if body does not start with workflow prefix', () => {
const matcher = previousCommentFor('Another Workflow')
expect(matcher(validComment)).toBe(false)
})

it('returns false if user is null', () => {
const matcher = previousCommentFor('Test Workflow')
const comment = { ...validComment, user: null }
expect(matcher(comment)).toBe(false)
})
})

describe('generateComment', () => {
it('returns fallback message when durationReport is undefined', () => {
const result = generateComment('My Workflow', undefined)
expect(result).toBe(
'🕒 Workflow "My Workflow" has no historical runs on master/main branch. Can\'t compare.'
)
})

it('generates correct comment for increase', () => {
const report: DurationReport = {
durationInSeconds: 90,
diffInSeconds: 30,
diffInPercentage: -50
}
const result = generateComment('My Workflow', report)
expect(result).toBe(
'🕒 Workflow "My Workflow" took 90s which is an increase with 30s (50.00%) compared to latest run on master/main.'
)
})

it('generates correct comment for decrease', () => {
const report: DurationReport = {
durationInSeconds: 60,
diffInSeconds: -20,
diffInPercentage: 25
}
const result = generateComment('Deploy', report)
expect(result).toBe(
'🕒 Workflow "Deploy" took 60s which is a decrease with 20s (25.00%) compared to latest run on master/main.'
)
})
})
65 changes: 65 additions & 0 deletions __tests__/durationCalculator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { jest } from '@jest/globals'
import { calculateDuration } from '../src/durationCalculator'

const fixedNow = new Date('2025-01-01T12:00:00Z').getTime()

beforeEach(() => {
jest.useFakeTimers().setSystemTime(fixedNow)
})

afterEach(() => {
jest.useRealTimers()
})

describe('durationAnalyzer', () => {
it('returns undefined if no last run is provided', () => {
const result = calculateDuration({
run_started_at: '2025-01-01T11:50:00Z',
updated_at: '2025-01-01T12:00:00Z'
})
expect(result).toBeUndefined()
})

it('throws error if current.run_started_at is missing', () => {
expect(() =>
calculateDuration(
{ updated_at: '2025-01-01T12:00:00Z' },
{
run_started_at: '2025-01-01T11:00:00Z',
updated_at: '2025-01-01T11:10:00Z'
}
)
).toThrow('Missing run_started_at')
})

it('throws error if last.run_started_at is missing', () => {
expect(() =>
calculateDuration(
{
run_started_at: '2025-01-01T11:50:00Z',
updated_at: '2025-01-01T12:00:00Z'
},
{ updated_at: '2025-01-01T11:10:00Z' }
)
).toThrow('Missing run_started_at')
})

it('calculates duration and difference correctly', () => {
const result = calculateDuration(
{
run_started_at: '2025-01-01T11:59:00Z',
updated_at: '2025-01-01T12:00:00Z'
},
{
run_started_at: '2025-01-01T11:00:00Z',
updated_at: '2025-01-01T11:10:00Z'
}
)

expect(result).toEqual({
durationInSeconds: 60,
diffInSeconds: -(9 * 60),
diffInPercentage: 90
})
})
})
166 changes: 163 additions & 3 deletions __tests__/main.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,165 @@
describe('main.ts', () => {
it('is truthy', () => {
expect(true).toBeTruthy()
import { jest } from '@jest/globals'
import type { GhActionsContext } from '../src/types'

const getCurrentWorkflowRun = jest.fn()
const listWorkflowRuns = jest.fn()
const listComments = jest.fn()
const createComment = jest.fn()
const updateComment = jest.fn()

jest.unstable_mockModule('../src/githubClient.js', () => ({
default: class {
getCurrentWorkflowRun = getCurrentWorkflowRun
listWorkflowRuns = listWorkflowRuns
listComments = listComments
createComment = createComment
updateComment = updateComment
}
}))

const DEFAULT_CONTEXT: GhActionsContext = {
eventName: '',
workflow: '',
issue: {
number: 0
},
repo: {
owner: '',
repo: ''
},
runId: 0
}

const DEFAULT_WORKFLOW_RUN = {
workflow_id: 0,
head_branch: '',
status: '',
conclusion: '',
run_started_at: '2025-04-29T14:05:00Z',
updated_at: '2025-04-29T14:07:00Z'
}

const fixedNow = new Date('2025-04-29T14:00:00Z')

describe('main', () => {
let run: typeof import('../src/main.js').run

beforeEach(async () => {
jest.useFakeTimers().setSystemTime(fixedNow.getTime())
const mainModule = await import('../src/main.js')
run = mainModule.run

listComments.mockReturnValue(
Promise.resolve({
data: []
})
)
listWorkflowRuns.mockReturnValue(
Promise.resolve({
data: {
workflow_runs: []
}
})
)
getCurrentWorkflowRun.mockReturnValue(
Promise.resolve({
data: {
...DEFAULT_WORKFLOW_RUN
}
})
)
})

afterEach(() => {
jest.useRealTimers()
})

it('does nothing for non-pull_request events', async () => {
await run(
{ ...DEFAULT_CONTEXT, eventName: 'not-pull_request' },
'fake-token'
)
expect(createComment).not.toHaveBeenCalled()
expect(updateComment).not.toHaveBeenCalled()
})

it('creates comment if no previous one is found', async () => {
await run(
{
...DEFAULT_CONTEXT,
eventName: 'pull_request',
workflow: 'My workflow'
},
'fake-token'
)

expect(createComment).toHaveBeenCalledWith(
'🕒 Workflow "My workflow" has no historical runs on master/main branch. Can\'t compare.'
)
})

it('updates comment if previous one is found', async () => {
listComments.mockReturnValueOnce(
Promise.resolve({
data: [
{
id: 42,
user: {
login: 'github-actions[bot]',
type: 'Bot'
},
body: '🕒 Workflow "Another workflow" took...'
}
]
})
)
await run(
{
...DEFAULT_CONTEXT,
eventName: 'pull_request',
workflow: 'Another workflow'
},
'fake-token'
)

expect(updateComment).toHaveBeenCalledWith(
42,
'🕒 Workflow "Another workflow" has no historical runs on master/main branch. Can\'t compare.'
)
})

it('includes duration information if previous workflow run is found', async () => {
getCurrentWorkflowRun.mockReturnValueOnce({
data: {
run_started_at: '2025-04-29T13:57:00Z',
workflow_id: 42
}
})
listWorkflowRuns.mockReturnValueOnce({
data: {
workflow_runs: [
{
...DEFAULT_WORKFLOW_RUN,
run_started_at: '2025-04-28T13:55:00Z',
updated_at: '2025-04-28T13:56:00Z',
head_branch: 'main',
status: 'completed',
conclusion: 'success'
}
]
}
})
await run(
{
...DEFAULT_CONTEXT,
eventName: 'pull_request',
workflow: 'Some workflow'
},
'fake-token'
)

expect(createComment).toHaveBeenCalledWith(
'🕒 Workflow "Some workflow" took 180s which is an increase with 120s (200.00%) compared to latest run on master/main.'
)
})
})
Loading