Skip to content

feat: Add disableLogging option to getManagementToken #770

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
92 changes: 92 additions & 0 deletions src/keys/get-management-token.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import { Logger } from '../utils'
import { sign } from 'jsonwebtoken'
import { LRUCache } from 'lru-cache'

// No longer need to mock debug globally
// sinon.stub(require('debug'), 'default').returns(mockDebugInstance)

const PRIVATE_KEY = fs.readFileSync(path.join(__dirname, '..', '..', 'keys', 'key.pem'), 'utf-8')
const APP_ID = 'app_id'
const SPACE_ID = 'space_id'
Expand All @@ -29,6 +32,95 @@ const noop = () => {}
const defaultCache: LRUCache<string, string> = new LRUCache({ max: 10 })

describe('getManagementToken', () => {
let loggerSpy: sinon.SinonSpy
let createLoggerStub: sinon.SinonStub

beforeEach(() => {
// Reset the spy before each test
loggerSpy = sinon.spy()
// Reset stubs if they exist
if (createLoggerStub) {
createLoggerStub.restore()
}
// Reset cache as well
defaultCache.clear()
})

afterEach(() => {
// Ensure stubs are restored after each test
if (createLoggerStub) {
createLoggerStub.restore()
}
})

it('fetches a token and logs by default', async () => {
const mockToken = 'token'
// Stub createLogger to return our spy directly, as debug instances are functions
const loggerUtils = await import('../utils/logger')
// The logger instance itself is the function to call for logging
createLoggerStub = sinon
.stub(loggerUtils, 'createLogger')
.returns(loggerSpy as unknown as Logger)

const post = sinon.stub()
post.resolves({ statusCode: 201, body: JSON.stringify({ token: mockToken }) })
const httpClient = { post } as unknown as HttpClient
// createGetManagementToken will now receive the loggerSpy via the stub
const getManagementTokenFn = createGetManagementToken(
loggerSpy as unknown as Logger,
httpClient,
defaultCache,
)

const result = await getManagementTokenFn(PRIVATE_KEY, DEFAULT_OPTIONS) // disableLogging is default false

assert.deepStrictEqual(result, mockToken)
assert(
post.calledWith(
`spaces/${SPACE_ID}/environments/${ENVIRONMENT_ID}/app_installations/${APP_ID}/access_tokens`,
sinon.match({ headers: { Authorization: sinon.match.string } }),
),
)
// Assert that the logger spy was called
assert(loggerSpy.called, 'Logger should have been called by default')
})

it('does not log when disableLogging is true', async () => {
const mockToken = 'token-no-log'
// Stub createLogger like before
const loggerUtils = await import('../utils/logger')
createLoggerStub = sinon
.stub(loggerUtils, 'createLogger')
.returns(loggerSpy as unknown as Logger)

const post = sinon.stub()
post.resolves({ statusCode: 201, body: JSON.stringify({ token: mockToken }) })
const httpClient = { post } as unknown as HttpClient
// createGetManagementToken receives the spy logger
const getManagementTokenFn = createGetManagementToken(
loggerSpy as unknown as Logger,
httpClient,
defaultCache,
)

const result = await getManagementTokenFn(PRIVATE_KEY, {
...DEFAULT_OPTIONS,
disableLogging: true,
})

assert.deepStrictEqual(result, mockToken)
assert(
post.calledWith(
`spaces/${SPACE_ID}/environments/${ENVIRONMENT_ID}/app_installations/${APP_ID}/access_tokens`,
sinon.match({ headers: { Authorization: sinon.match.string } }),
),
)
// Assert that the logger spy was NOT called
// Because disableLogging: true, the internal logic uses noOpLogger,
// so the log method of the passed-in mockLogger (our spy) is never invoked.
assert(!loggerSpy.called, 'Logger should not have been called when disableLogging is true')
})

it('fetches a token', async () => {
const mockToken = 'token'
const logger = noop as unknown as Logger
Expand Down
23 changes: 15 additions & 8 deletions src/keys/get-management-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,15 @@ export interface GetManagementTokenOptions {
keyId?: string
reuseToken?: boolean
host?: string
disableLogging?: boolean
}

let defaultCache: LRUCache<string, string> | undefined

// Create a logger with a specific namespace that is unlikely to be enabled,
// effectively making it a no-op logger while conforming to the Logger type.
const noOpLogger: Logger = createLogger({ filename: __filename + ':noop' })

/**
* Synchronously sign the given privateKey into a JSON Web Token string
* @internal
Expand Down Expand Up @@ -91,13 +96,13 @@ export const createGetManagementToken = (
throw new ReferenceError('Invalid privateKey: expected a string representing a private key')
}

if (opts.reuseToken === undefined) {
opts.reuseToken = true
}
const reuseToken = opts.reuseToken ?? true
const disableLogging = opts.disableLogging ?? false
const effectiveLog = disableLogging ? noOpLogger : log

const cacheKey =
opts.appInstallationId + opts.spaceId + opts.environmentId + privateKey.slice(32, 132)
if (opts.reuseToken) {
if (reuseToken) {
const existing = cache.get(cacheKey) as string
if (existing) {
return existing as string
Expand All @@ -107,10 +112,10 @@ export const createGetManagementToken = (
const appToken = generateOneTimeToken(
privateKey,
{ appId: opts.appInstallationId, keyId: opts.keyId },
{ log },
{ log: effectiveLog },
)
const ott = await getTokenFromOneTimeToken(appToken, opts, { log, http })
if (opts.reuseToken) {
const ott = await getTokenFromOneTimeToken(appToken, opts, { log: effectiveLog, http })
if (reuseToken) {
const decoded = decode(ott)
if (decoded && typeof decoded === 'object' && decoded.exp) {
// Internally expire cached tokens a bit earlier to make sure token isn't expired on arrival
Expand Down Expand Up @@ -152,9 +157,11 @@ export const getManagementToken = (privateKey: string, opts: GetManagementTokenO
defaultCache = new LRUCache({ max: 10 })
}
const httpClientOpts = typeof opts.host !== 'undefined' ? { prefixUrl: opts.host } : {}
const disableLogging = opts.disableLogging ?? false
const effectiveLog = disableLogging ? noOpLogger : createLogger({ filename: __filename })

return createGetManagementToken(
createLogger({ filename: __filename }),
effectiveLog,
createHttpClient(httpClientOpts),
defaultCache!,
)(privateKey, opts)
Expand Down