Skip to content

Commit

Permalink
add basic tests and flags
Browse files Browse the repository at this point in the history
  • Loading branch information
markandersontrocme committed Dec 11, 2024
1 parent b0822c1 commit b485686
Show file tree
Hide file tree
Showing 11 changed files with 416 additions and 12 deletions.
18 changes: 10 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
id: npm-ci-test
run: npm run ci-test

test-action:
test-action-build-project:
name: GitHub Actions Test
runs-on: ubuntu-latest

Expand All @@ -53,12 +53,14 @@ jobs:
id: checkout
uses: actions/checkout@v4

- name: Test Local Action
id: test-action
uses: ./
- name: Install up
uses: upbound/action-up@v1
with:
milliseconds: 2000
organization: github-actions-test
api-token: ${{ secrets.UP_API_TOKEN }}

- name: Print Output
id: output
run: echo "${{ steps.test-action.outputs.time }}"
- name: Build Upbound project
id: up-project-build
uses: ./
with:
project-file: test/project/upbound.yaml
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,5 @@ __tests__/runner/*
# IDE files
.idea
*.code-workspace

_output
101 changes: 101 additions & 0 deletions __tests__/main.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import * as core from '@actions/core'
import * as io from '@actions/io'
import { ToolRunner } from '@actions/exec/lib/toolrunner'

Check failure on line 3 in __tests__/main.test.ts

View workflow job for this annotation

GitHub Actions / Lint Codebase

'ToolRunner' is defined but never used
import * as run from '../src/main'

let debugMock: jest.SpiedFunction<typeof core.debug>

Check failure on line 6 in __tests__/main.test.ts

View workflow job for this annotation

GitHub Actions / Lint Codebase

'debugMock' is assigned a value but never used
let errorMock: jest.SpiedFunction<typeof core.error>

Check failure on line 7 in __tests__/main.test.ts

View workflow job for this annotation

GitHub Actions / Lint Codebase

'errorMock' is assigned a value but never used
let infoMock: jest.SpiedFunction<typeof core.info>

Check failure on line 8 in __tests__/main.test.ts

View workflow job for this annotation

GitHub Actions / Lint Codebase

'infoMock' is assigned a value but never used
let warningMock: jest.SpiedFunction<typeof core.warning>

Check failure on line 9 in __tests__/main.test.ts

View workflow job for this annotation

GitHub Actions / Lint Codebase

'warningMock' is assigned a value but never used
let setFailedMock: jest.SpiedFunction<typeof core.setFailed>

Check failure on line 10 in __tests__/main.test.ts

View workflow job for this annotation

GitHub Actions / Lint Codebase

'setFailedMock' is assigned a value but never used
let getInputMock: jest.SpiedFunction<typeof core.getInput>

Check failure on line 11 in __tests__/main.test.ts

View workflow job for this annotation

GitHub Actions / Lint Codebase

'getInputMock' is defined but never used

const whichMock = jest.spyOn(io, 'which')

let mockStatusCode: number
let stdOutMessage: string | undefined
let stdErrMessage: string | undefined

const mockExecFn = jest.fn().mockImplementation((toolPath, args, options) => {

Check failure on line 19 in __tests__/main.test.ts

View workflow job for this annotation

GitHub Actions / Lint Codebase

Functions that return promises must be async
if (options?.listeners?.stdout) {

Check failure on line 20 in __tests__/main.test.ts

View workflow job for this annotation

GitHub Actions / Lint Codebase

Unsafe member access .listeners on an `any` value
options.listeners.stdout(Buffer.from(stdOutMessage || '', 'utf8'))

Check failure on line 21 in __tests__/main.test.ts

View workflow job for this annotation

GitHub Actions / Lint Codebase

Unsafe call of a(n) `any` typed value
}
if (options?.listeners?.stderr) {
options.listeners.stderr(Buffer.from(stdErrMessage || '', 'utf8'))
}
return Promise.resolve(mockStatusCode)
})
jest.mock('@actions/exec/lib/toolrunner', () => {
return {
ToolRunner: jest.fn().mockImplementation((toolPath, args, options) => {
return {
exec: () => mockExecFn(toolPath, args, options)
}
})
}
})

const path = '/path/to/up'

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

mockStatusCode = 0
stdOutMessage = undefined
stdErrMessage = undefined

debugMock = jest.spyOn(core, 'debug').mockImplementation()
errorMock = jest.spyOn(core, 'error').mockImplementation()
infoMock = jest.spyOn(core, 'info').mockImplementation()
warningMock = jest.spyOn(core, 'warning').mockImplementation()
setFailedMock = jest.spyOn(core, 'setFailed').mockImplementation()
})

describe('getUpPath', () => {
it('returns the path to the up executable if found', async () => {
whichMock.mockResolvedValue(path)

const upPath = await run.getUpPath()

expect(whichMock).toHaveBeenCalledWith('up', false)
expect(upPath).toBe(path)
})

it('throws an error if the up executable is not found', async () => {
whichMock.mockResolvedValue('')

await expect(run.getUpPath()).rejects.toThrow(
'up not found, you can install it using upbound/action-up'
)
})
})

describe('verifyLogin', () => {
it('returns true if user is logged in', async () => {
stdOutMessage = JSON.stringify([{ id: 1, name: 'test-org' }])
mockStatusCode = 0

const isLoggedIn = await run.verifyLogin(path)
expect(isLoggedIn).toBe(true)
expect(mockExecFn).toHaveBeenCalledWith(
path,
['org', 'list', '--format', 'json'],
expect.any(Object)
)
})

it('returns false if user is not logged in', async () => {
stdErrMessage = 'Unauthorized'
mockStatusCode = 1

const isLoggedIn = await run.verifyLogin(path)
expect(isLoggedIn).toBe(false)
expect(mockExecFn).toHaveBeenCalledWith(
path,
['org', 'list', '--format', 'json'],
expect.any(Object)
)
})
})
})
12 changes: 12 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ inputs:
push-project:
description: 'Push Upbound project'
default: 'false'
project-file:
description: 'Path to project definition file. Default to upbound.yaml'
default: ''
repository:
description: 'Repository for the built package. Overrides the repository specified in the project file.'
default: ''
tag:
description: 'Tag for the built package. If not provided, a semver tag will be generated.'
default: ''
public:
description: 'Create new repositories with public visibility'
default: 'false'

branding:
color: 'purple'
Expand Down
79 changes: 75 additions & 4 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,29 @@ import * as io from '@actions/io'

const upToolname = 'up'

export async function run(): Promise<void> {
async function run(): Promise<void> {
try {
const upPath = await getUpPath()

const upProjectBuild = new ToolRunner(upPath, ['project', 'build'])
const isLoggedIn = await verifyLogin(upPath)
if (!isLoggedIn) {
core.setFailed('User is not logged in. Please log in to Upbound.')
}

const projectFile = core.getInput('project-file')
const repository = core.getInput('repository')
const tag = core.getInput('tag')
const publicVisibility = core.getInput('public')

const upProjectBuildArgs = ['project', 'build']
if (projectFile && projectFile.trim().length > 0) {
upProjectBuildArgs.push('--project-file', projectFile)
}
if (repository && repository.trim().length > 0) {
upProjectBuildArgs.push('--repository', repository)
}

const upProjectBuild = new ToolRunner(upPath, upProjectBuildArgs)
await upProjectBuild.exec()

const pushProject = core.getInput('push-project')
Expand All @@ -17,17 +35,70 @@ export async function run(): Promise<void> {
return
}

const upProjectPush = new ToolRunner(upPath, ['project', 'push'])
const upProjectPushArgs = ['project', 'push']
if (projectFile && projectFile.trim().length > 0) {
upProjectPushArgs.push('-f', projectFile)
}
if (repository && repository.trim().length > 0) {
upProjectPushArgs.push('--repository', repository)
}
if (tag && tag.trim().length > 0) {
upProjectPushArgs.push('--tag', tag)
}
if (publicVisibility.toLowerCase() === 'true') {
upProjectPushArgs.push('--public')
}

const upProjectPush = new ToolRunner(upPath, upProjectPushArgs)
await upProjectPush.exec()
} catch (error) {
if (error instanceof Error) core.setFailed(error.message)
}
}

export async function getUpPath() {
async function verifyLogin(upPath: string): Promise<boolean> {
try {
let output = ''
let errorOutput = ''

const upOrgList = new ToolRunner(
upPath,
['org', 'list', '--format', 'json'],
{
listeners: {
stdout: (data: Buffer) => {
output += data.toString()
},
stderr: (data: Buffer) => {
errorOutput += data.toString()
}
}
}
)

await upOrgList.exec()

const orgList = JSON.parse(output)

if (Array.isArray(orgList) && orgList.length > 0) {
core.info('User is logged in.')
return true
} else {
core.warning('User is not logged in. No organizations found.')
return false
}
} catch (error) {
core.warning('User is not logged in. Unauthorized error detected.')
return false
}
}

async function getUpPath(): Promise<string> {
const upPath = await io.which(upToolname, false)
if (!upPath)
throw Error('up not found, you can install it using upbound/action-up')

return upPath
}

export { run, verifyLogin, getUpPath }
3 changes: 3 additions & 0 deletions test/project/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
_output
.venv
.up
Loading

0 comments on commit b485686

Please sign in to comment.