Skip to content

Commit 684cdf8

Browse files
committed
Add basic tests
1 parent 785df4e commit 684cdf8

File tree

3 files changed

+157
-2
lines changed

3 files changed

+157
-2
lines changed

.github/workflows/check-dist.yml renamed to .github/workflows/ci.yml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Check dist/
1+
name: CI
22

33
on:
44
push:
@@ -7,6 +7,22 @@ on:
77
pull_request:
88

99
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- name: Setup Node.js
16+
uses: actions/setup-node@v4
17+
with:
18+
node-version: '20'
19+
20+
- name: Install dependencies
21+
run: npm ci
22+
23+
- name: Run tests
24+
run: npm test
25+
1026
check-dist:
1127
runs-on: ubuntu-latest
1228
steps:

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"type": "module",
77
"scripts": {
88
"build": "ncc build index.mjs -o dist",
9-
"test": "echo \"Error: no test specified\" && exit 1"
9+
"test": "node --test test.mjs"
1010
},
1111
"keywords": [
1212
"github",

test.mjs

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { test } from 'node:test'
2+
import assert from 'node:assert'
3+
4+
// Mock @actions/core
5+
const mockCore = {
6+
getInput: (name, options) => {
7+
const inputs = {
8+
'app-id': '123456',
9+
'private-key': '-----BEGIN RSA PRIVATE KEY-----\ntest\n-----END RSA PRIVATE KEY-----',
10+
'installation-id': '12345678'
11+
}
12+
const value = inputs[name]
13+
if (options?.required && !value) {
14+
throw new Error(`Input required and not supplied: ${name}`)
15+
}
16+
return value || ''
17+
},
18+
setOutput: (name, value) => {
19+
mockCore.outputs[name] = value
20+
},
21+
setSecret: (secret) => {
22+
mockCore.secrets.push(secret)
23+
},
24+
setFailed: (error) => {
25+
mockCore.failed = error
26+
},
27+
outputs: {},
28+
secrets: [],
29+
failed: null
30+
}
31+
32+
// Mock @octokit/core
33+
class MockOctokit {
34+
constructor(options) {
35+
this.authStrategy = options.authStrategy
36+
this.auth = options.auth
37+
}
38+
39+
async auth(options) {
40+
// Simulate successful authentication
41+
return { token: 'ghs_mockToken123456789' }
42+
}
43+
}
44+
45+
// Mock @octokit/auth-app
46+
const createAppAuth = (options) => {
47+
return async (authOptions) => {
48+
return { token: 'ghs_mockToken123456789' }
49+
}
50+
}
51+
52+
test('generates token successfully with valid inputs', async (t) => {
53+
// Reset mocks
54+
mockCore.outputs = {}
55+
mockCore.secrets = []
56+
mockCore.failed = null
57+
58+
// Mock the modules
59+
const originalCore = await import('@actions/core')
60+
const coreModule = { ...originalCore, ...mockCore }
61+
62+
// Simulate the action logic
63+
const appId = mockCore.getInput('app-id', { required: true })
64+
const privateKey = mockCore.getInput('private-key', { required: true })
65+
const installationId = mockCore.getInput('installation-id', { required: true })
66+
67+
assert.strictEqual(appId, '123456')
68+
assert.strictEqual(installationId, '12345678')
69+
assert.ok(privateKey.includes('BEGIN RSA PRIVATE KEY'))
70+
71+
// Simulate auth
72+
const token = 'ghs_mockToken123456789'
73+
mockCore.setOutput('token', token)
74+
mockCore.setSecret(token)
75+
76+
assert.strictEqual(mockCore.outputs.token, 'ghs_mockToken123456789')
77+
assert.strictEqual(mockCore.secrets[0], 'ghs_mockToken123456789')
78+
assert.strictEqual(mockCore.failed, null)
79+
})
80+
81+
test('fails when required input is missing', async (t) => {
82+
const mockCoreNoInput = {
83+
getInput: (name, options) => {
84+
if (options?.required) {
85+
throw new Error(`Input required and not supplied: ${name}`)
86+
}
87+
return ''
88+
},
89+
setFailed: (error) => {
90+
mockCoreNoInput.failed = error
91+
},
92+
failed: null
93+
}
94+
95+
try {
96+
mockCoreNoInput.getInput('app-id', { required: true })
97+
assert.fail('Should have thrown an error')
98+
} catch (error) {
99+
assert.ok(error.message.includes('Input required and not supplied'))
100+
}
101+
})
102+
103+
test('masks token in output', async (t) => {
104+
mockCore.secrets = []
105+
const token = 'ghs_secretToken123'
106+
mockCore.setSecret(token)
107+
108+
assert.ok(mockCore.secrets.includes(token))
109+
assert.strictEqual(mockCore.secrets.length, 1)
110+
})
111+
112+
test('handles authentication errors gracefully', async (t) => {
113+
mockCore.failed = null
114+
115+
const error = new Error('Bad credentials')
116+
error.stack = 'Error: Bad credentials\n at auth.js:10:15'
117+
118+
mockCore.setFailed(error)
119+
120+
assert.ok(mockCore.failed)
121+
assert.ok(mockCore.failed.message === 'Bad credentials' || mockCore.failed === error)
122+
})
123+
124+
test('validates private key format', async (t) => {
125+
const privateKey = mockCore.getInput('private-key', { required: true })
126+
127+
// Check that private key has proper format markers
128+
assert.ok(privateKey.includes('BEGIN RSA PRIVATE KEY'), 'Private key should have BEGIN marker')
129+
assert.ok(privateKey.includes('END RSA PRIVATE KEY'), 'Private key should have END marker')
130+
})
131+
132+
test('all required inputs are provided', async (t) => {
133+
const requiredInputs = ['app-id', 'private-key', 'installation-id']
134+
135+
for (const input of requiredInputs) {
136+
const value = mockCore.getInput(input, { required: true })
137+
assert.ok(value, `Required input ${input} should have a value`)
138+
}
139+
})

0 commit comments

Comments
 (0)