Skip to content

Commit

Permalink
Improve detailed summary
Browse files Browse the repository at this point in the history
  • Loading branch information
szapp committed Mar 21, 2024
1 parent 5aa04fc commit 329316d
Show file tree
Hide file tree
Showing 10 changed files with 2,299 additions and 204 deletions.
10 changes: 8 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
{
"root": true,
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:jest/recommended",
"prettier"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint"],
"plugins": ["jest", "@typescript-eslint"],
"rules": {
"eqeqeq": 2,
"no-duplicate-imports": "error",
Expand Down
1 change: 0 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,3 @@
/coverage/
/dist/
/node_modules/
/__tests__/
93 changes: 68 additions & 25 deletions __tests__/main.test.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,50 @@
import * as core from '@actions/core'
import * as cache from '@actions/cache'
import * as main from '../src/main'
import path from 'path'
import fs from 'fs'

// Mock the action's main function
const runMock = jest.spyOn(main, 'run')

// Other utilities
// Constants
const parVer = '5cfb63ab29df99073a9fcc551d42652bdb130c74'
const binDirName = '.parsiphae-action-bin'
const downloadDirName = '.parsiphae-action-source'

// Paths and environment variables
const cachePath = path.join(__dirname, 'CACHE')
const tempPath = path.join(__dirname, 'TEMP')
const stepSummaryPath = path.join(tempPath, 'step-summary.html')
const workspacePath = path.dirname(__dirname)
const binPath = path.join(workspacePath, '.parsiphae-action-bin')
const downloadPath = path.join(workspacePath, '.parsiphae-action-source')
const binPath = path.join(workspacePath, binDirName)
const downloadPath = path.join(workspacePath, downloadDirName)
const cleanPaths = [cachePath, tempPath, binPath, downloadPath]
const runnerOs = process.env['RUNNER_OS']
const runnerArch = process.env['RUNNER_ARCH']
process.env['RUNNER_TEMP'] = tempPath
process.env['RUNNER_TOOL_CACHE'] = cachePath
process.env['GITHUB_WORKSPACE'] = workspacePath
process.env['GITHUB_STEP_SUMMARY'] = stepSummaryPath

// Mock the GitHub Actions libraries
let debugMock: jest.SpiedFunction<typeof core.debug>
let noticeMock: jest.SpiedFunction<typeof core.notice>
let errorMock: jest.SpiedFunction<typeof core.error>
let getInputMock: jest.SpiedFunction<typeof core.getInput>
let getBooleanInputMock: jest.SpiedFunction<typeof core.getBooleanInput>
let setFailedMock: jest.SpiedFunction<typeof core.setFailed>
let saveCacheMock: jest.SpiedFunction<typeof cache.saveCache>
let restoreCacheMock: jest.SpiedFunction<typeof cache.restoreCache>
jest.spyOn(core, 'startGroup').mockImplementation()
jest.spyOn(core, 'endGroup').mockImplementation()

// Mock the GitHub API
const createCheckMock = jest.fn((_params) => ({ data: { details_url: 'https://example.com' } }))
jest.mock('@actions/github', () => {
return {
getOctokit: (_token: string) => {
return {
rest: {
checks: {
create: () => ({ data: { details_url: 'https://example.com' } }),
create: createCheckMock,
},
},
}
Expand All @@ -47,22 +59,17 @@ jest.mock('@actions/github', () => {
}
})

jest.mock('@actions/cache', () => {
return {
saveCache: async (_paths: string[], _key: string): Promise<number> => 0,
restoreCache: async (_paths: string[], _primaryKey: string): Promise<undefined> => undefined,
}
})

describe('action', () => {
beforeEach(async () => {
jest.clearAllMocks()

noticeMock = jest.spyOn(core, 'notice').mockImplementation()
errorMock = jest.spyOn(core, 'error').mockImplementation()
getInputMock = jest.spyOn(core, 'getInput').mockImplementation()
getBooleanInputMock = jest.spyOn(core, 'getBooleanInput').mockImplementation()
setFailedMock = jest.spyOn(core, 'setFailed').mockImplementation()
saveCacheMock = jest.spyOn(cache, 'saveCache').mockImplementation()
restoreCacheMock = jest.spyOn(cache, 'restoreCache').mockImplementation()
fs.mkdirSync(tempPath, { recursive: true })
fs.writeFileSync(stepSummaryPath, '')
})

afterAll(async () => {
Expand All @@ -73,7 +80,7 @@ describe('action', () => {
getInputMock.mockImplementation((name) => {
switch (name) {
case 'file':
return '__tests__/data/fail.d'
return '__tests__/data/*.d'
case 'check_name':
return 'Testing'
case 'token':
Expand All @@ -92,12 +99,52 @@ describe('action', () => {
}
})

const cacheKey = binDirName
const primaryKey = `${runnerOs}-${runnerArch}-parsiphae-${parVer}`
const expectedCheck1 = {
owner: 'owner',
repo: 'repo',
name: 'Testing: fail.d',
head_sha: 'sha',
started_at: expect.any(String),
completed_at: expect.any(String),
conclusion: 'failure',
output: {
title: '1 error',
summary: expect.stringMatching(/^Parsiphae found 1 syntax error \([^)]*\)$/),
text: expect.any(String),
annotations: [
{ annotation_level: 'failure', end_line: 3, message: 'Missing semicolon', path: '__tests__/data/fail.d', start_line: 3 },
],
},
}
const expectedCheck2 = {
owner: 'owner',
repo: 'repo',
name: 'Testing: pass.d',
head_sha: 'sha',
started_at: expect.any(String),
completed_at: expect.any(String),
conclusion: 'success',
output: {
title: 'No errors',
summary: expect.stringMatching(/^Parsiphae found no syntax errors \([^)]*\)$/),
text: expect.any(String),
annotations: [],
},
}
const expectedSummary =
/^<h1>Testing Results<\/h1>\r?\n\r?<table><tr><th>Test result 🔬<\/th><th>Source 📝<\/th><th>Errors <\/th><th>Files #<\/th><th>Duration <\/th><th>Details 📊<\/th><\/tr><tr><td>🔴 Fail<\/td><td>fail\.d<\/td><td>1<\/td><td>1<\/td><td>[^<]+<\/td><td><a href="https:\/\/example\.com">undefined<\/a><\/td><\/tr><tr><td>🟢 Pass<\/td><td>pass\.d<\/td><td>0<\/td><td>1<\/td><td>[^<]+<\/td><td><a href="https:\/\/example\.com">undefined<\/a><\/td><\/tr><\/table>\s*$/

await main.run()
expect(runMock).toHaveReturned()

expect(errorMock).not.toHaveBeenCalled()
expect(setFailedMock).not.toHaveBeenCalled()
expect(noticeMock).toHaveBeenNthCalledWith(1, 'Find the detailed Parsiphae (Testing) results at https://example.com')
expect(restoreCacheMock).toHaveBeenNthCalledWith(1, [cacheKey], primaryKey)
expect(saveCacheMock).toHaveBeenNthCalledWith(1, [cacheKey], primaryKey)
expect(createCheckMock).toHaveReturnedTimes(2)
expect(createCheckMock).toHaveBeenCalledWith(expect.objectContaining(expectedCheck1))
expect(createCheckMock).toHaveBeenCalledWith(expect.objectContaining(expectedCheck2))
expect(fs.readFileSync(stepSummaryPath, 'utf8')).toMatch(expectedSummary)
}, 120000)

it('sets a failed status for invalid input file pattern', async () => {
Expand All @@ -106,17 +153,15 @@ describe('action', () => {
getInputMock.mockImplementation((name) => {
switch (name) {
case 'file':
return 'this is not a file'
return relPath
default:
return ''
}
})

await main.run()
expect(runMock).toHaveReturned()

expect(setFailedMock).toHaveBeenNthCalledWith(1, `No file found matching '${relPath}'`)
expect(errorMock).not.toHaveBeenCalled()
})

it('sets a failed status for an input file with wrong file extension', async () => {
Expand All @@ -134,8 +179,6 @@ describe('action', () => {

await main.run()
expect(runMock).toHaveReturned()

expect(setFailedMock).toHaveBeenNthCalledWith(1, `Invalid file extension of '${fullPath}'`)
expect(errorMock).not.toHaveBeenCalled()
})
})
2 changes: 1 addition & 1 deletion badges/coverage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 329316d

Please sign in to comment.