Skip to content

Commit

Permalink
Merge pull request #349 from DEFRA/500303-make-common-octokit-config
Browse files Browse the repository at this point in the history
500303 make common octokit config
  • Loading branch information
feedmypixel authored Jan 24, 2025
2 parents 9ef449b + e1c825e commit d3915ac
Show file tree
Hide file tree
Showing 13 changed files with 244 additions and 67 deletions.
4 changes: 1 addition & 3 deletions .jest/setup-env-vars.js
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
// Mock private key - from https://github.com/DEFRA/cdp-local-environment/blob/9dc739301ead62a54e79a85e99b4ccb641dc7b0c/config/cdp-portal-backend.env#L5
process.env.GITHUB_API_AUTH_APP_PRIVATE_KEY =
'LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKS0FJQkFBS0NBZ0VBbll1ejQ1M3UwYjBkdG5NYjJUSnh1SVJMekxJS2p2VjVJMnZhem9XeGFFN3d3VnpZCnNGU2tnNzlaSGNWSEt2ZktoSXYwRWtVZmJSbzAwRGpucnRKclZkc2F1TWtNcDFjdEVHZjcyWkVpYUcyRGFNZzkKWG0yUlV4L2dKZnJlS2o2dGcybUsxd292TWQ1S3MvTjhnNXZXck8wbUdPREVYWHVDakw4MkZick9IeUNQQ1VQRApVbEtTc0xMazJnN0I5a1hHOEFsWHdjaVlmSmpMZ3JaQlBzSzhUVTh5cjA3V0ZqMTQ2S2lMN0Q5NGp3cElrNjMwCmFHekwrejZudkIzNFVFZzMwZ1RDMXRWdXkrM3JXN3Q2S0F1REJaK3hYTG9uN3dndXV4NnRoSXFGUVpRcE13SjIKSkdmNXZRNS9nSnZ4YVFQRE8zVTRtRWMxczR2b0hpNGtlNzQxczFGdjdHOENVa0lmY2dlb3lvN0E2RzNUOUs0NwpmKzNyTEhNT2szOVdudHBGa2luZ3pMVTRZK0FHVjQrZFJvV2QvemVjVndOUjcrV1lhQzJBK0JNeHlKOFhJUG9LCncvMk9IZjQ4WTQvQUIxblVSR2ZNOXpvMW5CaGFNZlJZV3ltT3BuSThxNW4rWGlkUjVCaWdmU2lmTGdSQ3RvZnAKaGNJd24wZnhGMHhLbGN4S0tTZkpoOGo4MHAvSUxYTkFzUUc5dm0zWkhxaENQc21OZkVxRUJBb1daS05kRUZ2YQpyakNkQm1hWVVONEl0bUo0NEVQWEsvTi9ScnZxMVJrRmJOd1p4cURoaVlFMm1VK2hMdjgrdXRGUWNSVTdLVWZ3ClZYd0swZkpkU3dnR0ZFMCtFb3RIY0s2Vm9lMXh6d1podThuSHI4bUs1MkwybmJ6bHBQdkJKNzRrSE44Q0F3RUEKQVFLQ0FnQXBIc2tWbkdlMG03RlJLU3M0SGdmN21xQTBMYlkreVFoVXUvWncyQWxOaWVraDl3dGh2cjN2MnpZMQo1SU5tVGlXd1FkMHpGWktWeGZUSjhraGFZM1o3Z3NRdlBkNk5JTjVVdldkSlNxM1o1dGVaTmtaNlNvdlhUK2NQCjBySkJBWG9GWmp0RVZGYXNJL0tJbElGSDBwbU9LaG02L1pPVE9NVUMybmVSNVYrZVZUK3dNZDBkdEFxd2p3algKZDJtZHoybVV4a04vQTAzMW0yWG0ySTRnQlBEa0ZzdEtZWC9VYnpnTE5jN0xtMmRxb2tyK0xMV3h1Yy9sNUYwZwpad3drWEMwaHBuZDZYbHZjWTExK1pHa0dZYmJSZFhSMEdPeUtZYTdjelMySi9pTzQwYVNOT1ZPL3ZkbTd4RGg0ClIzdVRwdFZDeEI1QTlMa3FBMWJTNWNWRS9Ra0R1eEk1cWFUYy9IcjYyVHRweFZUV0RqN0NtazhiekxodE9WNk8KM1lYODJZUHBuSVF3RDNqLytwRDhRSDFLSFdrRk42aDl2VFZZRktaQ3VqRU43LzJNNUNncWV6a2dWRmd2RVVxMQo5UGdnem9WbjZZbkdrM0hpVFZ3UHI0d0ZRcU5GQ3dxUk95VjVVOXJwTksva0Y4WEpvTlkxZHY2bjA4UmNwazE0CnYvSXV1Z2kraHFJVEN0MEpNVjdaVXlYNkJNTEM3b2lSS29JY3ZGTjJNQmxYeEdOWUJ5K2pmTVhVSG5OY1VHNncKdDdWMmFVMll4RDZ4YUJ1VVhjTFJ4MHp2VHQwZjJIZVBVK0UrRVNBNlFtZWl3RkNzNXBoOFhhVzg2VklhTWFDbwpwdzdzN2FJYTVXWGZWQVlHSFNBVG1hdEF6dzQyVjBHSmFGb0V1amhDTk03Y0EzWWdtUUtDQVFFQTJTblpQbGdHClpkaEdlZXloUTc3VHRETkZBQmpTbzJWNkZIZ1pTOWpteHlLd0ZaMEhpOHlDK0xDZDZzdTUvbW90ZlBNRG1LWSsKajlMMVlMcDRsRU5rZm5lT3F3MW1KemdKdkN5ZDZnRnZZaU03SU5IMkgxWFBPNmtMUHN5TjA0cXl2emZya1hzcgpZOG40M0lqSE9RaVNHVTRmNk82SmE2MTE2Tm90SHFzTnpnUHpkUXYvYzR3TEp4YUJxcXZ0WGJKWWRkTzYrSTFZCmFSbkVPYnVvYkxpKzVZMHlFL003OFBpWmg1aEVsczNBUDcwMmpyTkU2SUQ1OW5xM3lsOFFyMkNOUDRVdlR2SisKaSthc1kxN3gvS3hQNXgxNGt4bXo0ai9nVWF0bnF1Z3hhNVBNRElSbHUwSTJhTTh0Uks1d0lVZ2ZmM0EvV2ZXLwplY2s1WkZFVzkzZW1HUUtDQVFFQXViaHc5Q1pvTDVRM3BvUERHQ2pqckVuTndOVU43Y1IxRUlWdGVLeGlmOW82Cm9qd1ZnUXZPTERzcnVzZk1ERkNrU2pSRmIzME1DVWhmVkhXOVp0dlI5WncvTy9kazlEa1pVU0RPbS80djhjcXkKUkI2Q1lvL0x5N3pVMmU3R1dETTBSN25kS3pOVFQwOHVuQ1pzN2ZkZmRCa3JDUWI5dFY4RkpOY1pZN2YxNW1HdAp5V3QwNkt5aWVEVCtpYXJRWlUyVy9pd05DaldEZTVNcGlWVTRLQ3hkcG1rRkMwUExsMVA4b1MwQTRDMzZDYTRRCnVIYWFJT0dhWXhRM0txVmtwQlhnREJZUlhZKzhXcGFmb2lPVDhSUFk0Q2NQUEo5MVdtRy9mUVNzOGI0TlVRcU4KQmFzYjNSTGVxOTJiK2VKOXNUVktDRVZRZW9PREkyK1FrRjJJYXFHSnR3S0NBUUVBbnlBeEZhTytuR1lMems5OApJMzN5OXJvU3QyMTQrMDNpVkpsa1A5V2gxUTB2NWNaNHZ4R09idGhML3I1bGZXMGtBOGkyTythbE5SSXB4MFVjCkVkZ0lEVHRpQ1NqNlQ0YWFhNDV0OEFnNUs3b1JHNDErUVp2SkREaGtDeElzWW5QaFlvaUJUc3JvRW1qdXQvcHAKc1ByOHd4b2grN2ErYjI1ZFZkQjQrMTN2OGFPbmczN1ZJai9kOURoanIwSG4zcDVPZjVnMEN1alhmYnZwc3p3MAp2K1hueTZEWXJ4R3VQSGFOV1hSVDNnVEorR3FYVFoxQ3d2T3ZOZExhVmFtdk9qTVBqUm4wZm03bUYzRmhwRGJ3CkxtdTg2T2NKY1JDR1pTVFZKRUxxNWFyYWU2K0M5ekVVc2xCa09neUZhU3hBOGNJOWdrNG52YTE3THF3cUo3M2IKakYzYUVRS0NBUUF1ZTdaeHRVY3dXV1dRWEx3d3lOTXJKUkhGYU0zaXErQXM4V2hUNHJtSWpJTk9aY0Z3R2hkUgpSTUlPTHNHb2QycVhVL3ZwQ3FBL2xvaWRxQlp1cnlnZTFDdVRnN2tWMDFDOTJIczkyZUlBSDU1OHBESTRId3VBCkE5UTJjNmZiSFgzQlVnakVMa1YwdlRuS1JXZlFLN0VOYXRzMW1EVlo5dDFmdWlLVnJjNXpDaEdvTHlnRXNHaTkKczIzZDRRM2x2UVRFdXh2TWFWWnVVSWY5NG9GNnRKZi85WnNZbGJCWVFPSWpLUk5tQ0x2alBsamJBbnhUTElRcQp0ajJVZys3cmpyb1Q3RllPVjlKcHpmZElhcVUxbXFVV2ZWQTMrU1V6S1BIM2hYc1B5bVNrMndJdTRBUEtVbFcvCktHbElvdUtZdnVDM3J6bVVZR1FyTTFvNGQrQ1Q1N2lEQW9JQkFGdG80S1AyTWpCY2NabFEvbnBrVURFZ2p6MmkKREhLbVBHUU40WVNHTERzOUFZd3pXRmZNWjF1N1BUWlVXaXF0Q2ZpYXIzRE5BM3ZxenViYjRONVJTUm5TdEI4bQo4ZWJuRTNhaXRjMVJpUU9DL0hxYXpZN2dlekY5dUNibk9obGxuZzJEYjA1ejdrQzIwa096cU1ySzQrRXBGaXZSCkpzWEtIU2RvbHowRVFCcEpsT2Zjby9WKzlTK1FZWERlc0xUdUZMUm9yY2JSMkhVRk4zWElhRW0yYWhDZlpqbSsKL1ZFeW9wVnBZUTNsbUtDWWZrZ1pUckNTRG9wVXBZNXRGcGI4QkVYVlk0ZFVtZG5xa0loMlN2K2ZZM1cxV0pxTwoxemQvdm5LTGJMMHdsYU5mQW1IYjEweVY5V3c1aGowYUZWejFFQWh6a1BaM3RKOG45L21rWFd4TGZBYz0KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K'
process.env.GITHUB_API_AUTH_APP_PRIVATE_KEY = 'mock-private-key'
7 changes: 4 additions & 3 deletions src/config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -283,9 +283,10 @@ const config = convict({
},
baseUrl: {
doc: 'Override the gitHub base url for local testing',
format: '*',
env: 'GITHUB_BASE_URL',
default: null
format: String,
nullable: true,
default: null,
env: 'GITHUB_BASE_URL'
},
repos: {
cdpAppConfig: {
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/github/commit-github-file.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getLatestCommitSha } from '~/src/helpers/github/get-latest-commit-sha.js'
import { graphql } from '~/src/helpers/oktokit.js'
import { graphql } from '~/src/helpers/oktokit/oktokit.js'

/**
* @param {{owner: string, repo: string, branch: string, commitMessage: string, filePath: string, content: object, logger: import('pino').Logger}} options
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/github/delete-github-file.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getLatestCommitSha } from '~/src/helpers/github/get-latest-commit-sha.js'
import { graphql } from '~/src/helpers/oktokit.js'
import { graphql } from '~/src/helpers/oktokit/oktokit.js'
import { createLogger } from '~/src/helpers/logging/logger.js'

const logger = createLogger()
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/github/get-content.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { octokit } from '~/src/helpers/oktokit.js'
import { octokit } from '~/src/helpers/oktokit/oktokit.js'

async function getContent(owner, repo, filePath, ref = 'main') {
const { data } = await octokit.rest.repos.getContent({
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/github/get-latest-commit-sha.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { octokit } from '~/src/helpers/oktokit.js'
import { octokit } from '~/src/helpers/oktokit/oktokit.js'

async function getLatestCommitSha(owner, repo, branch = 'main') {
const { data } = await octokit.rest.git.getRef({
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/github/trigger-workflow.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { octokit } from '~/src/helpers/oktokit.js'
import { octokit } from '~/src/helpers/oktokit/oktokit.js'

import { config } from '~/src/config/index.js'

Expand Down
56 changes: 0 additions & 56 deletions src/helpers/oktokit.js

This file was deleted.

59 changes: 59 additions & 0 deletions src/helpers/oktokit/factory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { createAppAuth } from '@octokit/auth-app'

import { removeNil } from '~/src/helpers/remove-nill.js'
import { proxyFetch } from '~/src/helpers/proxy/proxy-fetch.js'

/**
* @typedef {object} GitHubConfig
* @property {string|null} baseUrl
* @property {object} app
* @property {string} app.id
* @property {string} app.privateKey
* @property {string} app.installationId
*/

/**
* @typedef {import('@octokit/core').Octokit} Octokit
* @typedef {import('@octokit/core').Constructor} Constructor
* @typedef {import('@octokit/graphql').graphql} graphql
*/

/**
* Builds the Octokit and graphql instances to be used across the app
* @param {Octokit & Constructor} OctokitExtra
* @param {GitHubConfig} gitHubConfig
* @returns {{octokit: Octokit, graphql: graphql}}
*/
function octokitFactory(OctokitExtra, gitHubConfig) {
const baseUrl = gitHubConfig.baseUrl
const auth = {
appId: gitHubConfig.app.id,
privateKey: Buffer.from(gitHubConfig.app.privateKey, 'base64').toString(
'utf8'
),
installationId: gitHubConfig.app.installationId
}

const octokit = new OctokitExtra(
removeNil({
authStrategy: createAppAuth,
auth,
request: { fetch: proxyFetch, baseUrl },
baseUrl
})
)

const graphql = octokit.graphql.defaults(
removeNil({
request: {
hook: octokit.auth.hook,
fetch: proxyFetch
},
baseUrl
})
)

return { octokit, graphql }
}

export { octokitFactory }
94 changes: 94 additions & 0 deletions src/helpers/oktokit/factory.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { createAppAuth } from '@octokit/auth-app'
import { octokitFactory } from '~/src/helpers/oktokit/factory.js'
import { proxyFetch } from '~/src/helpers/proxy/proxy-fetch.js'

const buildConfig = (baseUrl) => ({
...(baseUrl && { baseUrl }),
isStubbed: baseUrl !== undefined,
app: {
id: '123',
privateKey: 'bW9jay1wcml2YXRlLWtleQ==', // base64 encoded 'mock-private-key' string
installationId: '456'
}
})

describe('#octokitFactory', () => {
const mockGraphqlDefaults = jest.fn()
const mockOctokitExtra = jest.fn().mockReturnValue({
graphql: { defaults: mockGraphqlDefaults },
auth: { hook: 'mockAuthHook' }
})
const stubBaseUrl = 'http://cdp.127.0.0.1.sslip.io:3333'

describe('When stubbed', () => {
beforeEach(() => {
octokitFactory(mockOctokitExtra, buildConfig(stubBaseUrl))
})

test('Should call OctokitExtra for a stubbed octokit instance', () => {
expect(mockOctokitExtra).toHaveBeenCalledWith({
authStrategy: createAppAuth,
auth: {
appId: '123',
privateKey: 'mock-private-key',
installationId: '456'
},
request: {
fetch: proxyFetch,
baseUrl: stubBaseUrl
},
baseUrl: stubBaseUrl
})
})

test('Should call OctokitExtra for a stubbed graphql instance', () => {
expect(mockGraphqlDefaults).toHaveBeenCalledWith({
request: {
hook: 'mockAuthHook',
fetch: proxyFetch
},
baseUrl: stubBaseUrl
})
})
})

describe('When not stubbed', () => {
beforeEach(() => {
octokitFactory(mockOctokitExtra, buildConfig())
})

test('Should call OctokitExtra for a non stubbed octokit instance', () => {
expect(mockOctokitExtra).toHaveBeenCalledWith({
authStrategy: createAppAuth,
auth: {
appId: '123',
privateKey: 'mock-private-key',
installationId: '456'
},
request: {
fetch: proxyFetch
}
})
})

test('Octokit extra options should not include baseUrl', () => {
expect(mockOctokitExtra.mock.calls[0][0]).not.toHaveProperty('baseUrl')
expect(mockOctokitExtra.mock.calls[0][0].request).not.toHaveProperty(
'baseUrl'
)
})

test('Should call OctokitExtra for a non stubbed graphql instance', () => {
expect(mockGraphqlDefaults).toHaveBeenCalledWith({
request: {
hook: 'mockAuthHook',
fetch: proxyFetch
}
})
})

test('Octokit graphql defaults should not include baseUrl', () => {
expect(mockGraphqlDefaults.mock.calls[0][0]).not.toHaveProperty('baseUrl')
})
})
})
13 changes: 13 additions & 0 deletions src/helpers/oktokit/oktokit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Octokit } from '@octokit/core'
import { restEndpointMethods } from '@octokit/plugin-rest-endpoint-methods'
import { createPullRequest } from 'octokit-plugin-create-pull-request'

import { config } from '~/src/config/index.js'
import { octokitFactory } from '~/src/helpers/oktokit/factory.js'

const OctokitExtra = Octokit.plugin(restEndpointMethods, createPullRequest)
const githubConfig = config.get('github')

const { octokit, graphql } = octokitFactory(OctokitExtra, githubConfig)

export { octokit, graphql }
23 changes: 23 additions & 0 deletions src/helpers/remove-nill.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import isNil from 'lodash/isNil'

/**
* Remove null and undefined values from an object
* @param {object} obj
* @returns {object}
*/
function removeNil(obj) {
if (Array.isArray(obj)) {
return obj.map(removeNil).filter((value) => !isNil(value))
} else if (!isNil(obj) && typeof obj === 'object') {
return Object.entries(obj).reduce((cleaned, [key, value]) => {
const clean = removeNil(value)
if (!isNil(clean)) {
cleaned[key] = clean
}
return cleaned
}, {})
}
return obj
}

export { removeNil }
45 changes: 45 additions & 0 deletions src/helpers/remove-nill.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { removeNil } from '~/src/helpers/remove-nill.js'

describe('#removeNil', () => {
test('Should remove null and undefined values from a flat object', () => {
const input = { a: 1, b: null, c: undefined, d: 4 }
const expected = { a: 1, d: 4 }

expect(removeNil(input)).toEqual(expected)
})

test('Should remove null and undefined values from a nested object', () => {
const input = { a: 1, b: { c: null, d: 4, e: undefined }, f: 5 }
const expected = { a: 1, b: { d: 4 }, f: 5 }

expect(removeNil(input)).toEqual(expected)
})

test('Should remove null and undefined values from an array', () => {
const input = [1, null, 2, undefined, 3]
const expected = [1, 2, 3]

expect(removeNil(input)).toEqual(expected)
})

test('Should remove null and undefined values from a nested array', () => {
const input = [1, [null, 2, undefined], 3]
const expected = [1, [2], 3]

expect(removeNil(input)).toEqual(expected)
})

test('Should return the same value for non-object and non-array inputs', () => {
expect(removeNil(1)).toBe(1)
expect(removeNil(null)).toBeNull()
expect(removeNil(undefined)).toBeUndefined()
expect(removeNil('string')).toBe('string')
})

test('Should handle complex nested structures', () => {
const input = { a: [1, null, { b: undefined, c: 3 }], d: { e: [null, 4] } }
const expected = { a: [1, { c: 3 }], d: { e: [4] } }

expect(removeNil(input)).toEqual(expected)
})
})

0 comments on commit d3915ac

Please sign in to comment.