-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #64 from github/feelepxyz/fake-api-server
Add a fake dependabot-api server
- Loading branch information
Showing
10 changed files
with
3,230 additions
and
120 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| name: 'ci' | ||
| on: pull_request | ||
|
|
||
| jobs: | ||
| integration: | ||
| 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 | ||
| run: npm test "integration" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import Docker from 'dockerode' | ||
| import {UPDATER_IMAGE_NAME} from '../src/main' | ||
| import waitPort from 'wait-port' | ||
| import path from 'path' | ||
| import {spawn} from 'child_process' | ||
|
|
||
| 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 | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| export const runFakeDependabotApi = async (port: number): Promise<Function> => { | ||
| const server = spawn('node', [ | ||
| `${path.join(__dirname, 'server/server.js')}`, | ||
| `${port}` | ||
| ]) | ||
|
|
||
| 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}) | ||
|
|
||
| return (): void => { | ||
| server.kill() | ||
| } | ||
| } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| 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 = process.argv.slice(2)[0] || 9000 | ||
|
|
||
| // Sets up a fake dependabot-api using json-server | ||
| // | ||
| // Test it locally by running this script directly: | ||
| // | ||
| // $ node __tests__/server/server.js Running on http://localhost:9000 | ||
| // | ||
| // Verify it works: curl http://localhost:9000/update_jobs/1/details | ||
| // | ||
| // The 'id' attribute is significant for json-server and maps requests tp the | ||
| // 'id' key in the db.json for the resource, for example: | ||
| // | ||
| // - GET /update_jobs/1/details and GET /update_jobs/1 return hard-coded update | ||
| // job in db.json | ||
| // - GET /update_jobs/2 would 404 | ||
| // - POST /update_jobs {data: {...attrs}} would persist a new update job with id | ||
| // 2 | ||
|
|
||
| // 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.use(middlewares) | ||
|
|
||
| // Inject a legit GITHUB_TOKEN to increase rate limits fetching manifests from github | ||
| 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.post( | ||
| '/update_jobs/:id/create_pull_request', | ||
| jsonServer.bodyParser, | ||
| (req, res) => { | ||
| const data = {...req.body.data, id: req.params.id} | ||
| db.pull_requests.push(data) | ||
| router.db.write() | ||
|
|
||
| res.jsonp({}) | ||
| } | ||
| ) | ||
|
|
||
| // TEMP HACK: Always return 204 on post so the updater doesn't buil out | ||
| server.use(jsonServer.bodyParser, (req, res, next) => { | ||
| if (req.method === 'POST' && req.body.data) { | ||
| req.body = req.body.data | ||
| res.sendStatus(204) | ||
| return | ||
| } | ||
| next() | ||
| }) | ||
|
|
||
| // NOTE: These map to resources in db.json | ||
| server.use( | ||
| jsonServer.rewriter({ | ||
| '/update_jobs/:id/details': '/update_jobs/:id', | ||
| '/update_jobs/:id/credentials': '/credentials/:id', | ||
| '/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(router) | ||
| server.listen(SERVER_PORT, () => { | ||
| console.log(`json-server is running on http://localhost:${SERVER_PORT}`) | ||
| }) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,87 +1,81 @@ | ||
| import Docker from 'dockerode' | ||
| import fs from 'fs' | ||
| import path from 'path' | ||
| import {Updater} from '../src/updater' | ||
| import {UPDATER_IMAGE_NAME} from '../src/main' | ||
| import axios from 'axios' | ||
|
|
||
| import {APIClient, JobParameters} from '../src/api-client' | ||
| import {ImageService} from '../src/image-service' | ||
| import {UPDATER_IMAGE_NAME} from '../src/main' | ||
| import {Updater} from '../src/updater' | ||
|
|
||
| import {removeDanglingUpdaterContainers, runFakeDependabotApi} 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://localhost:${FAKE_SERVER_PORT}` | ||
| // Used from this action to get job details and credentials | ||
| const externalDependabotApiUrl = | ||
| process.env.DEPENDABOT_API_URL || fakeDependabotApiUrl | ||
| // Used from within the updater container to update the job state and create prs | ||
| const internalDockerHost = | ||
| process.platform === 'darwin' ? 'host.docker.internal' : 'localhost' | ||
| const internalDependabotApiUrl = | ||
| process.env.DEPENDABOT_API_URL || | ||
| `http://${internalDockerHost}:${FAKE_SERVER_PORT}` | ||
| const params = new JobParameters( | ||
| 1, | ||
| process.env.JOB_TOKEN || 'job-token', | ||
| process.env.CREDENTIALS_TOKEN || 'cred-token', | ||
| internalDependabotApiUrl | ||
| ) | ||
|
|
||
| const client = axios.create({baseURL: externalDependabotApiUrl}) | ||
| const apiClient = new APIClient(client, params) | ||
| const updater = new Updater(UPDATER_IMAGE_NAME, apiClient) | ||
|
|
||
| beforeAll(async () => { | ||
| if (process.env.CI) { | ||
| // Skip this test on CI, as it takes too long to download the image | ||
| // Skip the test when we haven't preloaded the updater image | ||
| if (process.env.SKIP_INTEGRATION_TESTS) { | ||
| return | ||
| } | ||
|
|
||
| await ImageService.pull(UPDATER_IMAGE_NAME) | ||
|
|
||
| if (externalDependabotApiUrl === fakeDependabotApiUrl) { | ||
| server = await runFakeDependabotApi(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() // teardown server process | ||
| await removeDanglingUpdaterContainers() | ||
| }) | ||
|
|
||
| jest.setTimeout(20000) | ||
| it('should fetch manifests', async () => { | ||
| if (process.env.CI) { | ||
| // Skip this test on CI, as it takes too long to download the image | ||
| jest.setTimeout(25000) | ||
| it('should run the updater and create a pull request', async () => { | ||
| // Skip the test when we haven't preloaded the updater image | ||
| if (process.env.SKIP_INTEGRATION_TESTS) { | ||
| 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() | ||
|
|
||
| // NOTE: This will not work when running against the actual dependabot-api | ||
| // Checks if the pr was persisted in the fake json-server | ||
| const res = await client.get('/pull_requests/1') | ||
|
|
||
| expect(res.status).toEqual(200) | ||
| expect(res.data.data.attributes['pr-title']).toEqual( | ||
| 'Bump fetch-factory from 0.0.1 to 0.2.1' | ||
| ) | ||
| }) | ||
| }) |
Oops, something went wrong.