Skip to content

Commit

Permalink
Add a fake dependabot-api server
Browse files Browse the repository at this point in the history
Attempted to set up a fake dependabot-api server using json-server.

The integration test now boots up a localhost server running json-server
that looks similar to dependabot-api allowing the updater to complete
the update.

I haven't fully grocked how it works but should be possible to get it to
persist the pr create from the updater and assert it from the test.
  • Loading branch information
Philip Harrison committed Jul 27, 2021
1 parent a47bd57 commit e60f3c5
Show file tree
Hide file tree
Showing 11 changed files with 3,230 additions and 106 deletions.
21 changes: 21 additions & 0 deletions .github/workflows/integration-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: 'build-test'
on: pull_request

jobs:
integration-ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.sha }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: GPR login
run: docker login docker.pkg.github.com -u x -p ${{ secrets.GPR_TOKEN }}
- name: GRP pull
run: docker pull docker.pkg.github.com/dependabot/dependabot-updater:latest
- run: |
npm ci
- name: Run integration test files
env:
DEPENDABOT_API_URL: 'http://host.docker.internal:9000'
run: npm test "integration"
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
- run: |
npm ci
- run: |
CI=true npm run all
SKIP_INTEGRATION_TESTS=true npm run all
test: # make sure the action works on a clean machine without building
runs-on: ubuntu-latest
steps:
Expand Down
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

> First, you'll need to have a reasonably modern version of `node` handy. This won't work with versions older than 9, for instance.
Install the dependencies
Prerequisites: docker

Install project dependencies

```bash
$ npm install
Expand All @@ -27,6 +29,22 @@ $ npm test
...
```

## Access the updater image

Create a [PAT](https://github.com/settings/tokens/new) (Personal Access Token) to pull the updater image. Check the `read:packages` permission.

Export the PAT:

```
export GPR_TOKEN=_pat_with_read_packages_
```

You should now be able to run the `updater-integration.ts` test:

```
jest __tests__/updater-integration.test.ts
```

## Change action.yml

The action.yml contains defines the inputs and output for your action.
Expand Down
17 changes: 17 additions & 0 deletions __tests__/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Docker from 'dockerode'
import {UPDATER_IMAGE_NAME} from '../src/main'

export const removeDanglingUpdaterContainers = async (): Promise<void> => {
const docker = new Docker()
const containers = (await docker.listContainers()) || []

for (const container of containers) {
if (container.Image.includes(UPDATER_IMAGE_NAME)) {
try {
await docker.getContainer(container.Id).remove({v: true, force: true})
} catch (e) {
// ignore
}
}
}
}
55 changes: 55 additions & 0 deletions __tests__/server/db.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"update_jobs": [
{
"id": 1,
"allowed-updates": [
{
"dependency-type": "direct",
"update-type": "all"
}
],
"credentials-metadata": [
{
"type": "git_source",
"host": "github.com"
}
],
"dependencies": null,
"existing-pull-requests": [],
"ignore-conditions": [],
"lockfile-only": false,
"max-updater-run-time": 2700,
"package-manager": "npm_and_yarn",
"source": {
"provider": "github",
"repo": "dsp-testing/dependabot-all-updates-test",
"directory": "/",
"branch": null,
"api-endpoint": "https://api.github.com/",
"hostname": "github.com"
},
"updating-a-pull-request": false,
"update-subdependencies": false,
"requirements-update-strategy": null,
"security-advisories": [],
"security-updates-only": false,
"vendor-dependencies": false,
"reject-external-code": false,
"experiments": {
"build-pull-request-message": true
},
"commit-message-options": {
"include-scope": null,
"prefix": null,
"prefix-development": null
},
"data": {
"base-commit-sha": "a8d9c08ff29358f80f6194b5909de9879741780d"
}
}
],
"credentials": [{"id": 1}],
"dependencies": [],
"update_job_errors": [],
"pull_requests": []
}
70 changes: 70 additions & 0 deletions __tests__/server/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#!/usr/bin/env node

const jsonServer = require('json-server')
const path = require('path')
const fs = require('fs')
const server = jsonServer.create()
const db = JSON.parse(fs.readFileSync(path.join(__dirname, 'db.json')))
const router = jsonServer.router(db)
const middlewares = jsonServer.defaults()

const SERVER_PORT = 9000

// NOTE: Serialise the response like dependabot-api
router.render = (_, res) => {
const id = res.locals.data.id
const data = {
attributes: res.locals.data
}
if (id) {
data.id = id
}
res.jsonp({
data
})
}

server.get('/update_jobs/:id/credentials', (_, res) => {
res.jsonp({
data: {
attributes: {
credentials: {
type: 'git_source',
host: 'github.com',
username: 'x-access-token',
password: process.env.GITHUB_TOKEN
}
}
}
})
})

server.use(
jsonServer.rewriter({
'/update_jobs/:id/details': '/update_jobs/:id',
'/update_jobs/:id/credentials': '/credentials/:id',
'/update_jobs/:id/create_pull_request': '/pull_requests',
'/update_jobs/:id/update_pull_request': '/pull_requests',
'/update_jobs/:id/close_pull_request': '/pull_requests',
'/update_jobs/:id/record_update_job_error': '/update_job_errors/:id',
'/update_jobs/:id/mark_as_processed': '/update_jobs/:id',
'/update_jobs/:id/update_dependency_list': '/dependencies/:id',
'/update_jobs/:id/record_package_manager_version': '/update_jobs/:id'
})
)

server.use(jsonServer.bodyParser)
// TEMP HACK: Always return 204 on post so the updater doesn't buil out
server.use((req, res, next) => {
if (req.method === 'POST' && req.body.data) {
req.body = req.body.data
res.sendStatus(204)
}
next()
})

server.use(middlewares)
server.use(router)
server.listen(SERVER_PORT, () => {
console.log(`JSON Server is running on http://localhost:${SERVER_PORT}`)
})
100 changes: 46 additions & 54 deletions __tests__/updater-integration.test.ts
Original file line number Diff line number Diff line change
@@ -1,87 +1,79 @@
import Docker from 'dockerode'
import fs from 'fs'
import path from 'path'
import axios from 'axios'
import waitPort from 'wait-port'
import {spawn} from 'child_process'

import {Updater} from '../src/updater'
import {UPDATER_IMAGE_NAME} from '../src/main'
import {APIClient, JobParameters} from '../src/api-client'
import {ImageService} from '../src/image-service'

import {removeDanglingUpdaterContainers} from './helpers'
const FAKE_SERVER_PORT = 9000

describe('Updater', () => {
let server: any

// To run the js-code itself against API:
// const params = {
// jobID: 1,
// jobToken: 'xxx',
// credentialsToken: 'xxx',
// dependabotAPI: 'http://host.docker.internal:3001'
// }
// const client = axios.create({baseURL: params.dependabotAPI})
// const api = new DependabotAPI(client, params)
// const updater = new Updater(UPDATER_IMAGE_NAME, api)

// This stubs out API calls from JS, but will run the updater against an API
// running on the specified API endpoint.
const mockAPIClient: any = {
getJobDetails: jest.fn(),
getCredentials: jest.fn(),
params: {
jobID: 1,
jobToken: process.env.JOB_TOKEN,
credentialsToken: process.env.CREDENTIALS_TOKEN,
dependabotAPIURL: 'http://host.docker.internal:3001'
}
}
const updater = new Updater(UPDATER_IMAGE_NAME, mockAPIClient)
// This runs the tests against a fake dependabot-api server using json-server
const fakeDependabotApiUrl = `http://host.docker.internal:${FAKE_SERVER_PORT}`
const dependabotApiUrl =
process.env.DEPENDABOT_API_URL || fakeDependabotApiUrl
const params = new JobParameters(
1,
process.env.JOB_TOKEN || 'job-token',
process.env.CREDENTIALS_TOKEN || 'cred-token',
process.env.DEPENDABOT_API_URL ||
`http://host.docker.internal:${FAKE_SERVER_PORT}`
)

const client = axios.create({baseURL: params.dependabotAPIURL})
const apiClient = new APIClient(client, params)
const updater = new Updater(UPDATER_IMAGE_NAME, apiClient)

beforeAll(async () => {
if (process.env.CI) {
if (process.env.SKIP_INTEGRATION_TESTS) {
// Skip this test on CI, as it takes too long to download the image
return
}

await ImageService.pull(UPDATER_IMAGE_NAME)

if (dependabotApiUrl === fakeDependabotApiUrl) {
server = spawn(`${path.join(__dirname, 'server/server.js')}`)
server.stdout.on('data', (data: any) => {
console.log(`json-server log: ${data}`) // eslint-disable-line no-console
})
server.stderr.on('data', (data: any) => {
console.error(`json-server error: ${data}`) // eslint-disable-line no-console
})
await waitPort({port: FAKE_SERVER_PORT})
}
})

afterEach(async () => {
const docker = new Docker()
const containers = (await docker.listContainers()) || []

for (const container of containers) {
if (
container.Image.includes(
'docker.pkg.github.com/dependabot/dependabot-updater'
)
) {
try {
await docker.getContainer(container.Id).remove({v: true, force: true})
} catch (e) {
// ignore
}
}
}
server && server.kill()
await removeDanglingUpdaterContainers()
})

jest.setTimeout(20000)
jest.setTimeout(30000)
it('should fetch manifests', async () => {
if (process.env.CI) {
if (process.env.SKIP_INTEGRATION_TESTS) {
// Skip this test on CI, as it takes too long to download the image
return
}

mockAPIClient.getJobDetails.mockImplementation(() => {
return JSON.parse(
fs
.readFileSync(path.join(__dirname, 'fixtures/job-details/npm.json'))
.toString()
).data.attributes
})
mockAPIClient.getCredentials.mockImplementation(() => {
return [
{
type: 'git_source',
host: 'github.com',
username: 'x-access-token',
password: process.env.GITHUB_TOKEN
}
]
})
await updater.runUpdater()

// TODO: Check if the pr was persisted in json-server
// const res = await client.get('/pull_requests/1')
// expect(res.status).toEqual(200)
})
})
Loading

0 comments on commit e60f3c5

Please sign in to comment.