diff --git a/package.json b/package.json index ffb74768..17d51e3e 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,9 @@ "dependencies": { "@adobe/aio-lib-core-config": "^5", "@adobe/aio-lib-core-networking": "^5", - "@adobe/aio-lib-runtime": "^7", + "@adobe/aio-lib-env": "^3.0.1", + "@adobe/aio-lib-ims": "^8.0.1", + "@adobe/aio-lib-runtime": "^7.1.0", "@oclif/core": "^1.3.0", "@types/jest": "^29.5.3", "chalk": "^4.1.2", diff --git a/src/RuntimeBaseCommand.js b/src/RuntimeBaseCommand.js index ca6f73b0..5395022f 100644 --- a/src/RuntimeBaseCommand.js +++ b/src/RuntimeBaseCommand.js @@ -18,6 +18,9 @@ const debug = createDebug('aio-cli-plugin-runtime') const http = require('http') const runtimeLib = require('@adobe/aio-lib-runtime') const config = require('@adobe/aio-lib-core-config') +const { getToken, context } = require('@adobe/aio-lib-ims') +const { getCliEnv } = require('@adobe/aio-lib-env') +const { CLI } = require('@adobe/aio-lib-ims/src/context') class RuntimeBaseCommand extends Command { async getOptions () { @@ -64,7 +67,21 @@ class RuntimeBaseCommand extends Command { async wsk (options) { if (!options) { + const authHandler = { + getAuthHeader: async () => { + await context.setCli({ 'cli.bare-output': true }, false) // set this globally + const env = getCliEnv() + console.debug(`Retrieving CLI Token using env=${env}`) + const accessToken = await getToken(CLI) + + return `Bearer ${accessToken}` + } + } options = await this.getOptions() + if (process.env.IS_DEPLOY_SERVICE_ENABLED === 'true') { + options.auth_handler = authHandler + options.apihost = options.apihost ?? PropertyDefault.DEPLOYSERVICEURL + } } return runtimeLib.init(options) } diff --git a/src/commands/runtime/action/create.js b/src/commands/runtime/action/create.js index a46b1a1e..6991d6de 100644 --- a/src/commands/runtime/action/create.js +++ b/src/commands/runtime/action/create.js @@ -11,10 +11,10 @@ governing permissions and limitations under the License. */ const fs = require('fs') -const RuntimeBaseCommand = require('../../../RuntimeBaseCommand') const { createKeyValueArrayFromFlag, createKeyValueArrayFromFile, createComponentsfromSequence, getKeyValueArrayFromMergedParameters } = require('@adobe/aio-lib-runtime').utils const { kindForFileExtension } = require('../../../kinds') const { Flags } = require('@oclif/core') +const RuntimeBaseCommand = require('../../../RuntimeBaseCommand') class ActionCreate extends RuntimeBaseCommand { isUpdate () { return false } diff --git a/src/properties.js b/src/properties.js index 1c007d6d..40cd7964 100644 --- a/src/properties.js +++ b/src/properties.js @@ -35,6 +35,7 @@ const PropertyEnv = { const PropertyDefault = { AUTH: '', APIHOST: 'https://adobeioruntime.net', + DEPLOYSERVICEURL: 'https://adobeioruntime.net', APIVERSION: 'v1', NAMESPACE: '_', CERT: '', diff --git a/test/RuntimeBaseCommand.test.js b/test/RuntimeBaseCommand.test.js index 987a1cc7..208bad92 100644 --- a/test/RuntimeBaseCommand.test.js +++ b/test/RuntimeBaseCommand.test.js @@ -15,6 +15,23 @@ const { Command } = require('@oclif/core') const { PropertyEnv } = require('../src/properties') const RuntimeLib = require('@adobe/aio-lib-runtime') const OpenWhiskError = require('openwhisk/lib/openwhisk_error') +const { getToken, context } = require('@adobe/aio-lib-ims') +const { getCliEnv } = require('@adobe/aio-lib-env') + +jest.mock('@adobe/aio-lib-ims', () => ({ + getToken: jest.fn(), + context: { + setCli: jest.fn() + } +})) + +jest.mock('@adobe/aio-lib-env', () => ({ + getCliEnv: jest.fn() +})) + +jest.mock('@adobe/aio-lib-runtime', () => ({ + init: jest.fn() +})) beforeEach(() => { fakeFileSystem.reset() @@ -50,6 +67,11 @@ describe('instance methods', () => { }) describe('init', () => { + const response = { + apihost: 'https://adobeioruntime.net', + api_key: 1234, + apiversion: 'v1' + } test('is a function', async () => { expect(command.init).toBeInstanceOf(Function) }) @@ -60,9 +82,7 @@ describe('instance methods', () => { fakeFileSystem.addJson(files) return command.wsk().then(() => { - expect(RuntimeLib.init).toHaveBeenLastCalledWith( - { apihost: 'https://adobeioruntime.net', api_key: 1234, apiversion: 'v1' } - ) + expect(RuntimeLib.init).toHaveBeenLastCalledWith(response) }) }) @@ -92,9 +112,7 @@ describe('instance methods', () => { fakeFileSystem.addJson(files) return command.wsk().then(() => { - expect(RuntimeLib.init).toHaveBeenLastCalledWith( - { api_key: 1234, apihost: 'https://adobeioruntime.net', apiversion: 'v1' } - ) + expect(RuntimeLib.init).toHaveBeenLastCalledWith(response) delete process.env[PropertyEnv.APIHOST] }) }) @@ -106,7 +124,11 @@ describe('instance methods', () => { return command.wsk().then(() => { expect(RuntimeLib.init).toHaveBeenLastCalledWith( - { api_key: 123, apihost: 'https://adobeioruntime.net', apiversion: 'v1' } + { + api_key: 123, + apihost: 'https://adobeioruntime.net', + apiversion: 'v1' + } ) delete process.env[PropertyEnv.APIHOST] }) @@ -312,4 +334,158 @@ describe('instance methods', () => { expect(command.error).toHaveBeenCalledWith('msg' + suffix) }) }) + + describe('authHandler', () => { + describe('when IS_DEPLOY_SERVICE_ENABLED = true', () => { + beforeEach(() => { + process.env.IS_DEPLOY_SERVICE_ENABLED = true + }) + + afterEach(() => { + process.env.IS_DEPLOY_SERVICE_ENABLED = false + }) + test('No Options : should return the correct Authorization header using getAuthHeader', async () => { + const mockToken = 'mock-access-token' + getToken.mockResolvedValue(mockToken) + + // Spy on runtimeLib.init to capture options before it's used + let capturedOptions + RuntimeLib.init.mockImplementation(async (options) => { + capturedOptions = options // Store options for later verification + return {} // Mock runtimeLib.init() return value + }) + + // Call wsk() which internally sets auth_handler + await command.wsk() + + // Ensure options were captured + expect(capturedOptions).toBeDefined() + expect(capturedOptions.auth_handler).toBeDefined() + expect(capturedOptions.apihost).toBeDefined() + expect(capturedOptions.apihost).toBe('some.host') + + // Call getAuthHeader() from captured options + const authHeader = await capturedOptions.auth_handler.getAuthHeader() + + expect(context.setCli).toHaveBeenCalledWith({ 'cli.bare-output': true }, false) + expect(getCliEnv).toHaveBeenCalled() + expect(getToken).toHaveBeenCalled() + expect(authHeader).toBe(`Bearer ${mockToken}`) + }) + + test('With Options : should return the correct Authorization header using getAuthHeader', async () => { + const mockToken = 'mock-access-token' + getToken.mockResolvedValue(mockToken) + + const options = { + auth_handler: { + getAuthHeader: async () => `Bearer ${mockToken}` + }, + apihost: 'https://custom-api.adobe.com' + } + + await command.wsk(options) // Call wsk() with an existing options object + + expect(RuntimeLib.init).toHaveBeenCalledWith(options) + }) + + test('Default OW Host testing', async () => { + delete process.env[PropertyEnv.APIHOST] + + const mockToken = 'mock-access-token' + getToken.mockResolvedValue(mockToken) + + command.getOptions = jest.fn().mockResolvedValue({}) + + // Mock runtimeLib.init to track its calls + const mockInit = jest.fn().mockResolvedValue({}) + RuntimeLib.init = mockInit + + // Call wsk() without options + await command.wsk() + + // Assertions + expect(RuntimeLib.init).toHaveBeenCalled() + + // Verify the passed options contain the default apihost + const optionsPassedToInit = mockInit.mock.calls[0][0] // Get the options passed to init + expect(optionsPassedToInit.apihost).toBe('https://adobeioruntime.net') + + // Ensure the Authorization header is set correctly + expect(optionsPassedToInit.auth_handler).toBeDefined() + const authHeader = await optionsPassedToInit.auth_handler.getAuthHeader() + expect(authHeader).toBe(`Bearer ${mockToken}`) + }) + }) + + describe('when IS_DEPLOY_SERVICE_ENABLED = false', () => { + beforeEach(() => { + process.env.IS_DEPLOY_SERVICE_ENABLED = false + }) + + test('No Options : should return the correct Authorization header using getAuthHeader', async () => { + const mockToken = 'mock-access-token' + getToken.mockResolvedValue(mockToken) + + // Spy on runtimeLib.init to capture options before it's used + let capturedOptions + RuntimeLib.init.mockImplementation(async (options) => { + capturedOptions = options // Store options for later verification + return {} // Mock runtimeLib.init() return value + }) + + // Call wsk() which internally sets auth_handler + await command.wsk() + + // Ensure options were captured + expect(capturedOptions).toBeDefined() + expect(capturedOptions.auth_handler).not.toBeDefined() + expect(capturedOptions.apihost).toBeDefined() + expect(capturedOptions.apihost).toBe('some.host') + }) + + test('With Options : should return the correct Authorization header using getAuthHeader', async () => { + const mockToken = 'mock-access-token' + getToken.mockResolvedValue(mockToken) + + const options = { + auth_handler: { + getAuthHeader: async () => `Bearer ${mockToken}` + }, + apihost: 'https://custom-api.adobe.com' + } + + await command.wsk(options) // Call wsk() with an existing options object + + expect(RuntimeLib.init).toHaveBeenCalledWith(options) + }) + + test('Default OW Host testing', async () => { + delete process.env[PropertyEnv.APIHOST] + + const mockToken = 'mock-access-token' + getToken.mockResolvedValue(mockToken) + + // command.getOptions = jest.fn().mockResolvedValue({}) + + // Mock runtimeLib.init to track its calls + const mockInit = jest.fn().mockResolvedValue({}) + RuntimeLib.init = mockInit + + // Call wsk() without options + await command.wsk() + + // Assertions + expect(RuntimeLib.init).toHaveBeenCalled() + + // Verify the passed options contain the default apihost + const optionsPassedToInit = mockInit.mock.calls[0][0] // Get the options passed to init + expect(optionsPassedToInit.apihost).toBe('some.host') + expect(optionsPassedToInit.namespace).toBe('some_namespace') + + // Ensure the Authorization header is set correctly + expect(optionsPassedToInit.auth_handler).not.toBeDefined() + }) + }) + }) })