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
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

yarn format
yarn build && git add dist
15 changes: 15 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Dependencies
node_modules/
.yarn/

# Build output
lib/
dist/

# Package files
package-lock.json
yarn.lock

# Other
.git/
*.md
6 changes: 6 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"tabWidth": 4,
"semi": false,
"singleQuote": true,
"trailingComma": "all"
}
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
## Overview

With this Github action, your [DevCycle](https://devcycle.com/) dashboard will be updated to display code snippets for each DevCycle variable usage within your project.

Note: This is intended to run when pushing changes to your main branch
Expand All @@ -8,6 +9,7 @@ Note: This is intended to run when pushing changes to your main branch
![Example Output](https://raw.githubusercontent.com/DevCycleHQ/feature-flag-code-usage-action/main/example_output.png)

### Usage

Create a new Actions workflow in your GitHub repository (e.g. devcycle-usages.yml) in the .github/workflows directory. In your new file, paste the following code:

```yaml
Expand Down Expand Up @@ -45,5 +47,6 @@ When referencing your API client ID and secret, we recommend using [GitHub Secre
| `client-secret` | yes | Your organization's API client secret, see [Organization Settings](https://app.devcycle.com/r/settings) |

### Configuration

The patterns used to identify references to variables in your code are fully customizable.
This action uses the [DevCycle CLI](https://github.com/DevCycleHQ/cli) under the hood, for details on how to configure the pattern matcher see the [CLI configuration](https://github.com/DevCycleHQ/cli#configuration).
99 changes: 53 additions & 46 deletions __tests__/action.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as action from '../src/action'


jest.mock('@actions/core')
jest.mock('@actions/exec')
jest.mock('@actions/github')
Expand All @@ -9,7 +8,7 @@
'github-token': 'token',
'project-key': 'my-project',
'client-id': 'id',
'client-secret': 'secret'
'client-secret': 'secret',
}

describe('run', () => {
Expand All @@ -32,19 +31,20 @@
core.getInput = jest.fn().mockImplementation((key) => mockInputs[key])
})

test.each([
'github-token', 'project-key', 'client-id', 'client-secret'
])('fails when missing parameter: %s', async (param) => {
const inputs = {...mockInputs}
delete inputs[param]
core.getInput = jest.fn().mockImplementation((key) => inputs[key])
await action.run()
test.each(['github-token', 'project-key', 'client-id', 'client-secret'])(
'fails when missing parameter: %s',
async (param) => {
const inputs = { ...mockInputs }
delete inputs[param]
core.getInput = jest.fn().mockImplementation((key) => inputs[key])
await action.run()

expect(core.setFailed).toBeCalledWith(`Missing ${param}`)
})
expect(core.setFailed).toBeCalledWith(`Missing ${param}`)
},
)

it('calls postCodeUsages for a valid request', async () => {
exec.getExecOutput = jest.fn().mockResolvedValue({stdout: '[]'})
exec.getExecOutput = jest.fn().mockResolvedValue({ stdout: '[]' })
await action.run()

expect(action.authenticate).not.toBeCalled()
Expand All @@ -54,32 +54,33 @@
})

describe('authenticate', () => {


beforeEach(() => {
jest.clearAllMocks()
})

it('sends authentication request', async () => {
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({access_token: '123'}),
ok: true
json: () => Promise.resolve({ access_token: '123' }),
ok: true,
}),
) as jest.Mock;
) as jest.Mock

const returnedToken = await action.authenticate('mock-client-id', 'mock-client-secret')
const returnedToken = await action.authenticate(
'mock-client-id',
'mock-client-secret',
)
const params = new URLSearchParams({
grant_type: 'client_credentials',
client_id: 'mock-client-id',
client_secret: 'mock-client-secret',
audience: 'https://api.devcycle.com/'
audience: 'https://api.devcycle.com/',
})


expect(fetch).toBeCalledWith(
'https://auth.devcycle.com/oauth/token',
expect.objectContaining({body: params.toString()}))
expect.objectContaining({ body: params.toString() }),
)
expect(returnedToken).toEqual('123')
})

Expand All @@ -88,12 +89,15 @@
Promise.resolve({
json: () => Promise.resolve('Some error'),
ok: false,
status: 401
status: 401,
}),
) as jest.Mock;
const authenticate = () => action.authenticate('mock-client-id', 'mock-client-secret')
) as jest.Mock
const authenticate = () =>
action.authenticate('mock-client-id', 'mock-client-secret')

await expect(authenticate).rejects.toThrow('Failed to authenticate with the DevCycle API. Check your credentials.')
await expect(authenticate).rejects.toThrow(
'Failed to authenticate with the DevCycle API. Check your credentials.',
)
})
})

Expand All @@ -115,49 +119,52 @@
github.context = {
repo: {
owner: 'mock-owner',
repo: 'mock-repo'
repo: 'mock-repo',
},
ref: 'refs/heads/main'
ref: 'refs/heads/main',
}
})

it('sends request to API', async () => {
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({}),
ok: true
ok: true,
}),
) as jest.Mock;
) as jest.Mock
await action.postCodeUsages([])

expect(action.authenticate).toBeCalledWith('id', 'secret')
expect(fetch).toBeCalledWith(
'https://api.devcycle.com/v1/projects/my-project/codeUsages',
{
body: JSON.stringify({
source: 'github',
repo: 'mock-owner/mock-repo',
branch: github.context.ref.split('/').pop(),
variables: []
}),
method: 'POST',
headers: expect.objectContaining({
Authorization: 'generated-token',
'dvc-referrer': 'github.code_usages'
})
})
{
body: JSON.stringify({
source: 'github',
repo: 'mock-owner/mock-repo',
branch: github.context.ref.split('/').pop(),
variables: [],
}),
method: 'POST',
headers: expect.objectContaining({
Authorization: 'generated-token',
'dvc-referrer': 'github.code_usages',
}),
},
)
})

it('fails if an error is thrown when sending code usages', async () => {
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve('Some error'),
ok: false
ok: false,
}),
) as jest.Mock;
) as jest.Mock

const postCodeUsages = () => action.postCodeUsages([])

await expect(postCodeUsages).rejects.toThrow('Failed to submit Code Usages.')
await expect(postCodeUsages).rejects.toThrow(
'Failed to submit Code Usages.',
)
})
})
})
Loading