From fc05ed9c5d837e9360c3d060ecb8a3161c1618a0 Mon Sep 17 00:00:00 2001 From: Michael Goberling Date: Tue, 28 Jan 2025 13:11:46 -0500 Subject: [PATCH 1/8] wip: use ims token to auth deploy actions --- package.json | 4 ++- src/RuntimeDeployCommand.js | 35 ++++++++++++++++++++++++++ src/commands/runtime/action/create.js | 6 ++--- src/commands/runtime/action/delete.js | 6 ++--- src/commands/runtime/api/create.js | 6 ++--- src/commands/runtime/api/delete.js | 6 ++--- src/commands/runtime/rule/create.js | 6 ++--- src/commands/runtime/rule/delete.js | 6 ++--- src/commands/runtime/trigger/create.js | 6 ++--- src/commands/runtime/trigger/delete.js | 6 ++--- 10 files changed, 62 insertions(+), 25 deletions(-) create mode 100644 src/RuntimeDeployCommand.js diff --git a/package.json b/package.json index 03ee7380..b660616f 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "dependencies": { "@adobe/aio-lib-core-config": "^5", "@adobe/aio-lib-core-networking": "^5", + "@adobe/aio-lib-ims": "^7.0.2", "@adobe/aio-lib-runtime": "^6", "@oclif/core": "^1.3.0", "@types/jest": "^29.5.3", @@ -100,5 +101,6 @@ "setupFilesAfterEnv": [ "./test/jest.setup.js" ] - } + }, + "packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610" } diff --git a/src/RuntimeDeployCommand.js b/src/RuntimeDeployCommand.js new file mode 100644 index 00000000..9aa289bd --- /dev/null +++ b/src/RuntimeDeployCommand.js @@ -0,0 +1,35 @@ +const RuntimeBaseCommand = require('./RuntimeBaseCommand') + +const { getToken, context } = require('@adobe/aio-lib-ims') +const { CLI } = require('@adobe/aio-lib-ims/src/context') +const { getCliEnv } = require('@adobe/aio-lib-env') +const runtimeLib = require('@adobe/aio-lib-runtime') + +class RuntimeDeployCommand extends RuntimeBaseCommand { + 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() + options.auth_handler = authHandler + options.apihost = "http://localhost:3000/runtime" + } + return runtimeLib.init(options) + } +} + +RuntimeDeployCommand.flags = { + ...RuntimeDeployCommand.flags +} + +module.exports = RuntimeDeployCommand diff --git a/src/commands/runtime/action/create.js b/src/commands/runtime/action/create.js index a46b1a1e..7de0235b 100644 --- a/src/commands/runtime/action/create.js +++ b/src/commands/runtime/action/create.js @@ -11,12 +11,12 @@ 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 RuntimeDeployCommand = require('../../../RuntimeDeployCommand') -class ActionCreate extends RuntimeBaseCommand { +class ActionCreate extends RuntimeDeployCommand { isUpdate () { return false } async run () { @@ -235,7 +235,7 @@ ActionCreate.args = [ ] ActionCreate.flags = { - ...RuntimeBaseCommand.flags, + ...RuntimeDeployCommand.flags, param: Flags.string({ char: 'p', description: 'parameter values in KEY VALUE format', // help description for flag diff --git a/src/commands/runtime/action/delete.js b/src/commands/runtime/action/delete.js index 31532a2e..3e131cb0 100644 --- a/src/commands/runtime/action/delete.js +++ b/src/commands/runtime/action/delete.js @@ -10,10 +10,10 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -const RuntimeBaseCommand = require('../../../RuntimeBaseCommand') +const RuntimeDeployCommand = require('../../../RuntimeDeployCommand') const { Flags } = require('@oclif/core') -class ActionDelete extends RuntimeBaseCommand { +class ActionDelete extends RuntimeDeployCommand { async run () { const { flags, args } = await this.parse(ActionDelete) const name = args.actionName @@ -37,7 +37,7 @@ ActionDelete.args = [ ] ActionDelete.flags = { - ...RuntimeBaseCommand.flags, + ...RuntimeDeployCommand.flags, json: Flags.boolean({ description: 'output raw json' }) diff --git a/src/commands/runtime/api/create.js b/src/commands/runtime/api/create.js index e6a2794c..4b8d9285 100644 --- a/src/commands/runtime/api/create.js +++ b/src/commands/runtime/api/create.js @@ -9,11 +9,11 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -const RuntimeBaseCommand = require('../../../RuntimeBaseCommand') +const RuntimeDeployCommand = require('../../../RuntimeDeployCommand') const { Flags } = require('@oclif/core') const fs = require('fs') -class ApiCreate extends RuntimeBaseCommand { +class ApiCreate extends RuntimeDeployCommand { async run () { const { args, flags } = await this.parse(ApiCreate) @@ -73,7 +73,7 @@ ApiCreate.args = [ ] ApiCreate.flags = { - ...RuntimeBaseCommand.flags, + ...RuntimeDeployCommand.flags, apiname: Flags.string({ char: 'n', description: 'Friendly name of the API; ignored when CFG_FILE is specified (default BASE_PATH)', diff --git a/src/commands/runtime/api/delete.js b/src/commands/runtime/api/delete.js index 2f3d08e6..8b9c999b 100644 --- a/src/commands/runtime/api/delete.js +++ b/src/commands/runtime/api/delete.js @@ -9,10 +9,10 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -const RuntimeBaseCommand = require('../../../RuntimeBaseCommand') +const RuntimeDeployCommand = require('../../../RuntimeDeployCommand') // eslint-disable-next-line no-unused-vars -class ApiDelete extends RuntimeBaseCommand { +class ApiDelete extends RuntimeDeployCommand { async run () { const { args } = await this.parse(ApiDelete) @@ -49,7 +49,7 @@ ApiDelete.args = [ ] ApiDelete.flags = { - ...RuntimeBaseCommand.flags + ...RuntimeDeployCommand.flags } ApiDelete.description = 'delete an API' diff --git a/src/commands/runtime/rule/create.js b/src/commands/runtime/rule/create.js index b01d4d47..ff566033 100644 --- a/src/commands/runtime/rule/create.js +++ b/src/commands/runtime/rule/create.js @@ -9,10 +9,10 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -const RuntimeBaseCommand = require('../../../RuntimeBaseCommand') +const RuntimeDeployCommand = require('../../../RuntimeDeployCommand') const { Flags } = require('@oclif/core') -class RuleCreate extends RuntimeBaseCommand { +class RuleCreate extends RuntimeDeployCommand { isUpdate () { return false } async run () { @@ -53,7 +53,7 @@ RuleCreate.args = [ ] RuleCreate.flags = { - ...RuntimeBaseCommand.flags, + ...RuntimeDeployCommand.flags, json: Flags.boolean({ description: 'output raw json' }) diff --git a/src/commands/runtime/rule/delete.js b/src/commands/runtime/rule/delete.js index fd8e7a2e..f9048bf4 100644 --- a/src/commands/runtime/rule/delete.js +++ b/src/commands/runtime/rule/delete.js @@ -9,10 +9,10 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -const RuntimeBaseCommand = require('../../../RuntimeBaseCommand') +const RuntimeDeployCommand = require('../../../RuntimeDeployCommand') const { Flags } = require('@oclif/core') -class RuleDelete extends RuntimeBaseCommand { +class RuleDelete extends RuntimeDeployCommand { async run () { const { flags, args } = await this.parse(RuleDelete) try { @@ -39,7 +39,7 @@ RuleDelete.args = [ ] RuleDelete.flags = { - ...RuntimeBaseCommand.flags, + ...RuntimeDeployCommand.flags, json: Flags.boolean({ description: 'output raw json' }) diff --git a/src/commands/runtime/trigger/create.js b/src/commands/runtime/trigger/create.js index 16022071..379e46ae 100644 --- a/src/commands/runtime/trigger/create.js +++ b/src/commands/runtime/trigger/create.js @@ -10,11 +10,11 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -const RuntimeBaseCommand = require('../../../RuntimeBaseCommand') +const RuntimeDeployCommand = require('../../../RuntimeDeployCommand') const { getKeyValueArrayFromMergedParameters } = require('@adobe/aio-lib-runtime').utils const { Flags } = require('@oclif/core') -class TriggerCreate extends RuntimeBaseCommand { +class TriggerCreate extends RuntimeDeployCommand { isUpdate () { return false } async run () { @@ -62,7 +62,7 @@ TriggerCreate.args = [ ] TriggerCreate.flags = { - ...RuntimeBaseCommand.flags, + ...RuntimeDeployCommand.flags, param: Flags.string({ char: 'p', description: 'parameter values in KEY VALUE format', // help description for flag diff --git a/src/commands/runtime/trigger/delete.js b/src/commands/runtime/trigger/delete.js index 9d56e45e..509262d3 100644 --- a/src/commands/runtime/trigger/delete.js +++ b/src/commands/runtime/trigger/delete.js @@ -10,10 +10,10 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -const RuntimeBaseCommand = require('../../../RuntimeBaseCommand') +const RuntimeDeployCommand = require('../../../RuntimeDeployCommand') const { parsePathPattern } = require('@adobe/aio-lib-runtime').utils -class TriggerDelete extends RuntimeBaseCommand { +class TriggerDelete extends RuntimeDeployCommand { async run () { const { args } = await this.parse(TriggerDelete) const triggerPath = args.triggerPath @@ -38,7 +38,7 @@ TriggerDelete.args = [ ] TriggerDelete.flags = { - ...RuntimeBaseCommand.flags + ...RuntimeDeployCommand.flags } TriggerDelete.description = 'Delete a trigger for Adobe I/O Runtime' From 05db31363b175d2e3bd349dcc500756cc0548441 Mon Sep 17 00:00:00 2001 From: Amulya Kashyap Date: Mon, 17 Feb 2025 17:51:50 +0530 Subject: [PATCH 2/8] changing api_host --- src/RuntimeDeployCommand.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/RuntimeDeployCommand.js b/src/RuntimeDeployCommand.js index 9aa289bd..ea74c61c 100644 --- a/src/RuntimeDeployCommand.js +++ b/src/RuntimeDeployCommand.js @@ -5,6 +5,11 @@ const { CLI } = require('@adobe/aio-lib-ims/src/context') const { getCliEnv } = require('@adobe/aio-lib-env') const runtimeLib = require('@adobe/aio-lib-runtime') +/** + * Class representing a command to deploy runtime. + * + * @extends RuntimeBaseCommand + */ class RuntimeDeployCommand extends RuntimeBaseCommand { async wsk(options) { if (!options) { @@ -22,7 +27,7 @@ class RuntimeDeployCommand extends RuntimeBaseCommand { } options = await this.getOptions() options.auth_handler = authHandler - options.apihost = "http://localhost:3000/runtime" + options.apihost = env.API_HOST ?? "http://localhost:3000/runtime" } return runtimeLib.init(options) } From 2a114d20f293ca20b4d06b186cec3a013214dd5e Mon Sep 17 00:00:00 2001 From: Amulya Kashyap Date: Mon, 17 Feb 2025 20:26:31 +0530 Subject: [PATCH 3/8] updated test code --- package.json | 1 + src/RuntimeDeployCommand.js | 38 +- test/RuntimeDeployCommand.test.js | 437 ++++++++++++++++++++++ test/commands/runtime/rule/create.test.js | 6 +- test/commands/runtime/rule/delete.test.js | 6 +- test/commands/runtime/rule/update.test.js | 6 +- 6 files changed, 465 insertions(+), 29 deletions(-) create mode 100644 test/RuntimeDeployCommand.test.js diff --git a/package.json b/package.json index ec0e83c0..33ce6c8f 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "dependencies": { "@adobe/aio-lib-core-config": "^5", "@adobe/aio-lib-core-networking": "^5", + "@adobe/aio-lib-env": "^3.0.1", "@adobe/aio-lib-ims": "^7.0.2", "@adobe/aio-lib-runtime": "^7", "@oclif/core": "^1.3.0", diff --git a/src/RuntimeDeployCommand.js b/src/RuntimeDeployCommand.js index ea74c61c..a30c921c 100644 --- a/src/RuntimeDeployCommand.js +++ b/src/RuntimeDeployCommand.js @@ -7,34 +7,32 @@ const runtimeLib = require('@adobe/aio-lib-runtime') /** * Class representing a command to deploy runtime. - * - * @extends RuntimeBaseCommand + * + * @augments RuntimeBaseCommand */ class RuntimeDeployCommand extends RuntimeBaseCommand { - async wsk(options) { - if (!options) { - const authHandler = { - getAuthHeader: async () => { - await context.setCli({ 'cli.bare-output': true }, false) // set this globally + 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) - const env = getCliEnv() - - console.debug(`Retrieving CLI Token using env=${env}`) - const accessToken = await getToken(CLI) - - return `Bearer ${accessToken}` - } - } - options = await this.getOptions() - options.auth_handler = authHandler - options.apihost = env.API_HOST ?? "http://localhost:3000/runtime" + return `Bearer ${accessToken}` } - return runtimeLib.init(options) + } + options = await this.getOptions() + options.auth_handler = authHandler + options.apihost = options.apihost ?? 'https://adobeioruntime.net' } + return runtimeLib.init(options) + } } RuntimeDeployCommand.flags = { - ...RuntimeDeployCommand.flags + ...RuntimeDeployCommand.flags } module.exports = RuntimeDeployCommand diff --git a/test/RuntimeDeployCommand.test.js b/test/RuntimeDeployCommand.test.js new file mode 100644 index 00000000..532f7698 --- /dev/null +++ b/test/RuntimeDeployCommand.test.js @@ -0,0 +1,437 @@ +/* +Copyright 2024 Adobe Inc. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const TheCommand = require('../src/RuntimeDeployCommand.js') +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() +}) + +test('exports', async () => { + expect(typeof TheCommand).toEqual('function') + expect(TheCommand.prototype).toBeInstanceOf(Command) +}) + +test('description', async () => { + expect(TheCommand.description).not.toBeDefined() +}) + +test('aliases', async () => { + expect(TheCommand.aliases).toEqual([]) +}) + +test('flags', async () => { + expect(Object.keys(TheCommand.flags)).toEqual(expect.arrayContaining( + ['cert', 'key', 'apiversion', 'apihost', 'auth', 'insecure', 'debug', 'verbose', 'version', 'help', 'useragent'])) +}) + +test('args', async () => { + expect(TheCommand.args).toBeUndefined() +}) + +describe('instance methods', () => { + let command + + beforeEach(() => { + command = new TheCommand([]) + }) + + describe('init', () => { + test('is a function', async () => { + expect(command.init).toBeInstanceOf(Function) + }) + + test('apihost default', async () => { + const files = {} + files[require('path').join(require('os').homedir(), '.wskprops')] = 'AUTH=1234' + fakeFileSystem.addJson(files) + + return command.wsk().then(() => { + expect(RuntimeLib.init).toHaveBeenLastCalledWith( + { + apihost: 'https://adobeioruntime.net', + api_key: 1234, + apiversion: 'v1', + auth_handler: expect.objectContaining({ + getAuthHeader: expect.any(Function) + }) + } + ) + }) + }) + + test('empty APIHOST should throw', async () => { + process.env[PropertyEnv.APIHOST] = ' ' + await expect(command.wsk()).rejects.toThrow(new Error('An API host must be specified')) + }) + + test('null AUTH should throw', async () => { + delete process.env[PropertyEnv.APIHOST] + delete process.env[PropertyEnv.AUTH] + const files = {} + files[require('path').join(require('os').homedir(), '.wskprops')] = '' + fakeFileSystem.addJson(files) + await expect(command.wsk()).rejects.toThrow(new Error('An AUTH key must be specified')) + }) + + test('empty AUTH should throw', async () => { + process.env[PropertyEnv.AUTH] = ' ' + await expect(command.wsk()).rejects.toThrow(new Error('An AUTH key must be specified')) + delete process.env[PropertyEnv.AUTH] + }) + + test('not string AUTH should not throw', async () => { + const files = {} + files[require('path').join(require('os').homedir(), '.wskprops')] = 'AUTH=1234' + fakeFileSystem.addJson(files) + + return command.wsk().then(() => { + expect(RuntimeLib.init).toHaveBeenLastCalledWith( + { + api_key: 1234, + apihost: 'https://adobeioruntime.net', + apiversion: 'v1', + auth_handler: expect.objectContaining({ + getAuthHeader: expect.any(Function) + }) + } + ) + delete process.env[PropertyEnv.APIHOST] + }) + }) + + test('auth with inline comment should trim the comment', async () => { + const files = {} + files[require('path').join(require('os').homedir(), '.wskprops')] = 'AUTH=123 #inline-comment' + fakeFileSystem.addJson(files) + + return command.wsk().then(() => { + expect(RuntimeLib.init).toHaveBeenLastCalledWith( + { + api_key: 123, + apihost: 'https://adobeioruntime.net', + apiversion: 'v1', + auth_handler: expect.objectContaining({ + getAuthHeader: expect.any(Function) + }) + } + ) + delete process.env[PropertyEnv.APIHOST] + }) + }) + + test('apihost flag with env', async () => { + const value = 'http://my-server' + process.env[PropertyEnv.APIHOST] = value + + return command.wsk().then(() => { + expect(RuntimeLib.init).toHaveBeenLastCalledWith( + // the values are from the wsk.properties fixture + { + api_key: 'some-gibberish-not-a-real-key', + namespace: 'some_namespace', + apihost: value, + apiversion: 'v1', + auth_handler: expect.objectContaining({ + getAuthHeader: expect.any(Function) + }) + } + ) + delete process.env[PropertyEnv.APIHOST] + }) + }) + + test('auth flag with env', async () => { + const value = 'a9329ekasgk01' + process.env[PropertyEnv.AUTH] = value + + return command.wsk().then(() => { + expect(RuntimeLib.init).toHaveBeenLastCalledWith( + // the values are from the wsk.properties fixture + { + api_key: value, + namespace: 'some_namespace', + apihost: 'some.host', + apiversion: 'v1', + auth_handler: expect.objectContaining({ + getAuthHeader: expect.any(Function) + }) + } + ) + delete process.env[PropertyEnv.AUTH] + }) + }) + + test('apiversion flag with env', async () => { + const value = 'v2' + process.env[PropertyEnv.APIVERSION] = value + + return command.wsk().then(() => { + expect(RuntimeLib.init).toHaveBeenLastCalledWith( + // the values are from the wsk.properties fixture + { + api_key: 'some-gibberish-not-a-real-key', + namespace: 'some_namespace', + apihost: 'some.host', + apiversion: value, + auth_handler: expect.objectContaining({ + getAuthHeader: expect.any(Function) + }) + } + ) + delete process.env[PropertyEnv.APIVERSION] + }) + }) + + test('verbose flag', async () => { + const debug = require('debug') + const spy = jest.spyOn(debug, 'enable').mockReturnValue() + + command.argv = ['--verbose'] + return command.init().then(() => { + expect(spy).toHaveBeenLastCalledWith('*') + spy.mockClear() + }) + }) + + test('debug flag', async () => { + const debug = require('debug') + const spy = jest.spyOn(debug, 'enable').mockReturnValue() + + command.argv = ['--debug', 'foo,bar'] + return command.init().then(() => { + expect(spy).toHaveBeenLastCalledWith('foo,bar') + spy.mockClear() + }) + }) + + test('init no flag', async () => { + const debug = require('debug') + const spy = jest.spyOn(debug, 'enable').mockReturnValue() + + command.argv = [] + return command.init().then(() => { + expect(spy).not.toHaveBeenCalled() + spy.mockClear() + }) + }) + }) + + describe('ow', () => { + test('is a function', async () => { + expect(command.wsk).toBeInstanceOf(Function) + }) + + test('returns a promise', () => { + return command.wsk().then((ow) => { + expect(ow).toBe(ow) + }) + }) + + // eslint-disable-next-line jest/expect-expect + test('no config file, no problem', () => { + fakeFileSystem.clear() + process.env[PropertyEnv.AUTH] = '1234' + + return command.wsk().then(() => { + delete process.env[PropertyEnv.AUTH] + }) + }) + + // eslint-disable-next-line jest/expect-expect + test('should not throw if config file specified but doesnt exist', () => { + fakeFileSystem.clear() + process.env[PropertyEnv.CONFIG_FILE] = '/foo' + process.env[PropertyEnv.AUTH] = '1234' + + return command.wsk().then(() => { + delete process.env[PropertyEnv.CONFIG_FILE] + delete process.env[PropertyEnv.AUTH] + }) + }) + }) + + describe('handleError', () => { + const suffix = '\n specify --verbose flag for more information' + test('is a function', async () => { + expect(command.handleError).toBeInstanceOf(Function) + }) + + test('calls error', async () => { + command.error = jest.fn() + command.argv = ['--verbose'] + await command.handleError('msg', new Error('an error')) + expect(command.error).toHaveBeenCalledWith('msg: an error' + suffix) + }) + + test('optional error object', async () => { + command.error = jest.fn() + await command.handleError('msg') + expect(command.error).toHaveBeenCalledWith('msg') + }) + + test('with no arguments', async () => { + command.error = jest.fn() + await command.handleError() + expect(command.error).toHaveBeenCalledWith('unknown error') + }) + + test('openwhisk error', async () => { + command.error = jest.fn() + await command.handleError('msg', new OpenWhiskError('an error')) + expect(command.error).toHaveBeenCalledWith('msg: an error' + suffix) + }) + + test('openwhisk error, with internal error', async () => { + command.error = jest.fn() + const err = new Error('is') + err.error = 'The real error' + await command.handleError('msg', new OpenWhiskError('what', err)) + expect(command.error).toHaveBeenCalledWith('msg: the real error' + suffix) + }) + + test('openwhisk error, with internal error and statusCode', async () => { + command.error = jest.fn() + const err = new Error('is') + err.error = 'The real error' + await command.handleError('msg', new OpenWhiskError('what', err, 404)) + expect(command.error).toHaveBeenCalledWith('msg: the real error (404 Not Found)' + suffix) + }) + + test('openwhisk error, with internal error and code', async () => { + command.error = jest.fn() + const err = new Error('is') + err.error = 'The real error' + err.code = 12 + await command.handleError('msg', new OpenWhiskError('what', err)) + expect(command.error).toHaveBeenCalledWith('msg: the real error (12)' + suffix) + }) + + test('openwhisk error, with status code', async () => { + command.error = jest.fn() + const err = new Error('is') + err.error = 'The real error' + await command.handleError('msg', new OpenWhiskError('what', null, 401)) + expect(command.error).toHaveBeenCalledWith('msg: 401 Unauthorized' + suffix) + }) + + test('openwhisk error, with weird status code', async () => { + command.error = jest.fn() + const err = new Error('is') + err.error = 'The real error' + await command.handleError('msg', new OpenWhiskError('what', null, 999999)) + expect(command.error).toHaveBeenCalledWith('msg: 999999' + suffix) + }) + + test('with no message', async () => { + command.error = jest.fn() + await command.handleError('msg', new Error('')) + expect(command.error).toHaveBeenCalledWith('msg' + suffix) + }) + }) + + describe('authHandler', () => { + 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}`) + }) + }) +}) diff --git a/test/commands/runtime/rule/create.test.js b/test/commands/runtime/rule/create.test.js index 46ff20bc..27c2ea02 100644 --- a/test/commands/runtime/rule/create.test.js +++ b/test/commands/runtime/rule/create.test.js @@ -11,14 +11,14 @@ governing permissions and limitations under the License. */ const TheCommand = require('../../../../src/commands/runtime/rule/create.js') -const RuntimeBaseCommand = require('../../../../src/RuntimeBaseCommand.js') +const RuntimeDeployCommand = require('../../../../src/RuntimeDeployCommand.js') const rtAction = 'rules.create' const { stdout } = require('stdout-stderr') const RuntimeLib = require('@adobe/aio-lib-runtime') test('exports', async () => { expect(typeof TheCommand).toEqual('function') - expect(TheCommand.prototype instanceof RuntimeBaseCommand).toBeTruthy() + expect(TheCommand.prototype instanceof RuntimeDeployCommand).toBeTruthy() }) test('description', async () => { @@ -51,7 +51,7 @@ test('args', async () => { // eslint-disable-next-line jest/expect-expect test('base flags included in command flags', - createTestBaseFlagsFunction(TheCommand, RuntimeBaseCommand) + createTestBaseFlagsFunction(TheCommand, RuntimeDeployCommand) ) test('flags', async () => { diff --git a/test/commands/runtime/rule/delete.test.js b/test/commands/runtime/rule/delete.test.js index e266ca01..ebe62592 100644 --- a/test/commands/runtime/rule/delete.test.js +++ b/test/commands/runtime/rule/delete.test.js @@ -11,14 +11,14 @@ governing permissions and limitations under the License. */ const TheCommand = require('../../../../src/commands/runtime/rule/delete.js') -const RuntimeBaseCommand = require('../../../../src/RuntimeBaseCommand.js') +const RuntimeDeployCommand = require('../../../../src/RuntimeDeployCommand.js') const rtAction = 'rules.delete' const { stdout } = require('stdout-stderr') const RuntimeLib = require('@adobe/aio-lib-runtime') test('exports', async () => { expect(typeof TheCommand).toEqual('function') - expect(TheCommand.prototype instanceof RuntimeBaseCommand).toBeTruthy() + expect(TheCommand.prototype instanceof RuntimeDeployCommand).toBeTruthy() }) test('description', async () => { @@ -43,7 +43,7 @@ test('args', async () => { // eslint-disable-next-line jest/expect-expect test('base flags included in command flags', - createTestBaseFlagsFunction(TheCommand, RuntimeBaseCommand) + createTestBaseFlagsFunction(TheCommand, RuntimeDeployCommand) ) test('flags', async () => { diff --git a/test/commands/runtime/rule/update.test.js b/test/commands/runtime/rule/update.test.js index b91a6469..26d9ed15 100644 --- a/test/commands/runtime/rule/update.test.js +++ b/test/commands/runtime/rule/update.test.js @@ -11,14 +11,14 @@ governing permissions and limitations under the License. */ const TheCommand = require('../../../../src/commands/runtime/rule/update.js') -const RuntimeBaseCommand = require('../../../../src/RuntimeBaseCommand.js') +const RuntimeDeployCommand = require('../../../../src/RuntimeDeployCommand.js') const rtAction = 'rules.update' const { stdout } = require('stdout-stderr') const RuntimeLib = require('@adobe/aio-lib-runtime') test('exports', async () => { expect(typeof TheCommand).toEqual('function') - expect(TheCommand.prototype instanceof RuntimeBaseCommand).toBeTruthy() + expect(TheCommand.prototype instanceof RuntimeDeployCommand).toBeTruthy() }) test('description', async () => { @@ -51,7 +51,7 @@ test('args', async () => { // eslint-disable-next-line jest/expect-expect test('base flags included in command flags', - createTestBaseFlagsFunction(TheCommand, RuntimeBaseCommand) + createTestBaseFlagsFunction(TheCommand, RuntimeDeployCommand) ) test('flags', async () => { From 87138820290ed4a5111fd406dc0e9619a1f5643b Mon Sep 17 00:00:00 2001 From: Amulya Kashyap Date: Tue, 18 Feb 2025 12:06:21 +0530 Subject: [PATCH 4/8] Updated PR comments --- package.json | 6 +- src/RuntimeBaseCommand.js | 15 + src/RuntimeDeployCommand.js | 38 -- src/commands/runtime/action/create.js | 6 +- src/commands/runtime/action/delete.js | 6 +- src/commands/runtime/api/create.js | 6 +- src/commands/runtime/api/delete.js | 6 +- src/commands/runtime/rule/create.js | 6 +- src/commands/runtime/rule/delete.js | 6 +- src/commands/runtime/trigger/create.js | 6 +- src/commands/runtime/trigger/delete.js | 6 +- test/RuntimeBaseCommand.test.js | 134 ++++++- test/RuntimeDeployCommand.test.js | 437 ---------------------- test/commands/runtime/rule/create.test.js | 6 +- test/commands/runtime/rule/delete.test.js | 6 +- test/commands/runtime/rule/update.test.js | 6 +- 16 files changed, 180 insertions(+), 516 deletions(-) delete mode 100644 src/RuntimeDeployCommand.js delete mode 100644 test/RuntimeDeployCommand.test.js diff --git a/package.json b/package.json index 33ce6c8f..a2c69c56 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "@adobe/aio-lib-core-networking": "^5", "@adobe/aio-lib-env": "^3.0.1", "@adobe/aio-lib-ims": "^7.0.2", - "@adobe/aio-lib-runtime": "^7", + "@adobe/aio-lib-runtime": "^7.0.2", "@oclif/core": "^1.3.0", "@types/jest": "^29.5.3", "chalk": "^4.1.2", @@ -103,5 +103,7 @@ "./test/jest.setup.js" ] }, - "packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610" + "overrides": { + "@adobe/aio-lib-state": "5.1.0" + } } diff --git a/src/RuntimeBaseCommand.js b/src/RuntimeBaseCommand.js index ca6f73b0..3236f06c 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,19 @@ 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() + options.auth_handler = authHandler + options.apihost = options.apihost ?? 'https://adobeioruntime.net' } return runtimeLib.init(options) } diff --git a/src/RuntimeDeployCommand.js b/src/RuntimeDeployCommand.js deleted file mode 100644 index a30c921c..00000000 --- a/src/RuntimeDeployCommand.js +++ /dev/null @@ -1,38 +0,0 @@ -const RuntimeBaseCommand = require('./RuntimeBaseCommand') - -const { getToken, context } = require('@adobe/aio-lib-ims') -const { CLI } = require('@adobe/aio-lib-ims/src/context') -const { getCliEnv } = require('@adobe/aio-lib-env') -const runtimeLib = require('@adobe/aio-lib-runtime') - -/** - * Class representing a command to deploy runtime. - * - * @augments RuntimeBaseCommand - */ -class RuntimeDeployCommand extends RuntimeBaseCommand { - 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() - options.auth_handler = authHandler - options.apihost = options.apihost ?? 'https://adobeioruntime.net' - } - return runtimeLib.init(options) - } -} - -RuntimeDeployCommand.flags = { - ...RuntimeDeployCommand.flags -} - -module.exports = RuntimeDeployCommand diff --git a/src/commands/runtime/action/create.js b/src/commands/runtime/action/create.js index 7de0235b..6991d6de 100644 --- a/src/commands/runtime/action/create.js +++ b/src/commands/runtime/action/create.js @@ -14,9 +14,9 @@ const fs = require('fs') const { createKeyValueArrayFromFlag, createKeyValueArrayFromFile, createComponentsfromSequence, getKeyValueArrayFromMergedParameters } = require('@adobe/aio-lib-runtime').utils const { kindForFileExtension } = require('../../../kinds') const { Flags } = require('@oclif/core') -const RuntimeDeployCommand = require('../../../RuntimeDeployCommand') +const RuntimeBaseCommand = require('../../../RuntimeBaseCommand') -class ActionCreate extends RuntimeDeployCommand { +class ActionCreate extends RuntimeBaseCommand { isUpdate () { return false } async run () { @@ -235,7 +235,7 @@ ActionCreate.args = [ ] ActionCreate.flags = { - ...RuntimeDeployCommand.flags, + ...RuntimeBaseCommand.flags, param: Flags.string({ char: 'p', description: 'parameter values in KEY VALUE format', // help description for flag diff --git a/src/commands/runtime/action/delete.js b/src/commands/runtime/action/delete.js index 3e131cb0..31532a2e 100644 --- a/src/commands/runtime/action/delete.js +++ b/src/commands/runtime/action/delete.js @@ -10,10 +10,10 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -const RuntimeDeployCommand = require('../../../RuntimeDeployCommand') +const RuntimeBaseCommand = require('../../../RuntimeBaseCommand') const { Flags } = require('@oclif/core') -class ActionDelete extends RuntimeDeployCommand { +class ActionDelete extends RuntimeBaseCommand { async run () { const { flags, args } = await this.parse(ActionDelete) const name = args.actionName @@ -37,7 +37,7 @@ ActionDelete.args = [ ] ActionDelete.flags = { - ...RuntimeDeployCommand.flags, + ...RuntimeBaseCommand.flags, json: Flags.boolean({ description: 'output raw json' }) diff --git a/src/commands/runtime/api/create.js b/src/commands/runtime/api/create.js index 4b8d9285..e6a2794c 100644 --- a/src/commands/runtime/api/create.js +++ b/src/commands/runtime/api/create.js @@ -9,11 +9,11 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -const RuntimeDeployCommand = require('../../../RuntimeDeployCommand') +const RuntimeBaseCommand = require('../../../RuntimeBaseCommand') const { Flags } = require('@oclif/core') const fs = require('fs') -class ApiCreate extends RuntimeDeployCommand { +class ApiCreate extends RuntimeBaseCommand { async run () { const { args, flags } = await this.parse(ApiCreate) @@ -73,7 +73,7 @@ ApiCreate.args = [ ] ApiCreate.flags = { - ...RuntimeDeployCommand.flags, + ...RuntimeBaseCommand.flags, apiname: Flags.string({ char: 'n', description: 'Friendly name of the API; ignored when CFG_FILE is specified (default BASE_PATH)', diff --git a/src/commands/runtime/api/delete.js b/src/commands/runtime/api/delete.js index 8b9c999b..2f3d08e6 100644 --- a/src/commands/runtime/api/delete.js +++ b/src/commands/runtime/api/delete.js @@ -9,10 +9,10 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -const RuntimeDeployCommand = require('../../../RuntimeDeployCommand') +const RuntimeBaseCommand = require('../../../RuntimeBaseCommand') // eslint-disable-next-line no-unused-vars -class ApiDelete extends RuntimeDeployCommand { +class ApiDelete extends RuntimeBaseCommand { async run () { const { args } = await this.parse(ApiDelete) @@ -49,7 +49,7 @@ ApiDelete.args = [ ] ApiDelete.flags = { - ...RuntimeDeployCommand.flags + ...RuntimeBaseCommand.flags } ApiDelete.description = 'delete an API' diff --git a/src/commands/runtime/rule/create.js b/src/commands/runtime/rule/create.js index ff566033..b01d4d47 100644 --- a/src/commands/runtime/rule/create.js +++ b/src/commands/runtime/rule/create.js @@ -9,10 +9,10 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -const RuntimeDeployCommand = require('../../../RuntimeDeployCommand') +const RuntimeBaseCommand = require('../../../RuntimeBaseCommand') const { Flags } = require('@oclif/core') -class RuleCreate extends RuntimeDeployCommand { +class RuleCreate extends RuntimeBaseCommand { isUpdate () { return false } async run () { @@ -53,7 +53,7 @@ RuleCreate.args = [ ] RuleCreate.flags = { - ...RuntimeDeployCommand.flags, + ...RuntimeBaseCommand.flags, json: Flags.boolean({ description: 'output raw json' }) diff --git a/src/commands/runtime/rule/delete.js b/src/commands/runtime/rule/delete.js index f9048bf4..fd8e7a2e 100644 --- a/src/commands/runtime/rule/delete.js +++ b/src/commands/runtime/rule/delete.js @@ -9,10 +9,10 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -const RuntimeDeployCommand = require('../../../RuntimeDeployCommand') +const RuntimeBaseCommand = require('../../../RuntimeBaseCommand') const { Flags } = require('@oclif/core') -class RuleDelete extends RuntimeDeployCommand { +class RuleDelete extends RuntimeBaseCommand { async run () { const { flags, args } = await this.parse(RuleDelete) try { @@ -39,7 +39,7 @@ RuleDelete.args = [ ] RuleDelete.flags = { - ...RuntimeDeployCommand.flags, + ...RuntimeBaseCommand.flags, json: Flags.boolean({ description: 'output raw json' }) diff --git a/src/commands/runtime/trigger/create.js b/src/commands/runtime/trigger/create.js index 379e46ae..16022071 100644 --- a/src/commands/runtime/trigger/create.js +++ b/src/commands/runtime/trigger/create.js @@ -10,11 +10,11 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -const RuntimeDeployCommand = require('../../../RuntimeDeployCommand') +const RuntimeBaseCommand = require('../../../RuntimeBaseCommand') const { getKeyValueArrayFromMergedParameters } = require('@adobe/aio-lib-runtime').utils const { Flags } = require('@oclif/core') -class TriggerCreate extends RuntimeDeployCommand { +class TriggerCreate extends RuntimeBaseCommand { isUpdate () { return false } async run () { @@ -62,7 +62,7 @@ TriggerCreate.args = [ ] TriggerCreate.flags = { - ...RuntimeDeployCommand.flags, + ...RuntimeBaseCommand.flags, param: Flags.string({ char: 'p', description: 'parameter values in KEY VALUE format', // help description for flag diff --git a/src/commands/runtime/trigger/delete.js b/src/commands/runtime/trigger/delete.js index 509262d3..9d56e45e 100644 --- a/src/commands/runtime/trigger/delete.js +++ b/src/commands/runtime/trigger/delete.js @@ -10,10 +10,10 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -const RuntimeDeployCommand = require('../../../RuntimeDeployCommand') +const RuntimeBaseCommand = require('../../../RuntimeBaseCommand') const { parsePathPattern } = require('@adobe/aio-lib-runtime').utils -class TriggerDelete extends RuntimeDeployCommand { +class TriggerDelete extends RuntimeBaseCommand { async run () { const { args } = await this.parse(TriggerDelete) const triggerPath = args.triggerPath @@ -38,7 +38,7 @@ TriggerDelete.args = [ ] TriggerDelete.flags = { - ...RuntimeDeployCommand.flags + ...RuntimeBaseCommand.flags } TriggerDelete.description = 'Delete a trigger for Adobe I/O Runtime' diff --git a/test/RuntimeBaseCommand.test.js b/test/RuntimeBaseCommand.test.js index 987a1cc7..f20972d0 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() @@ -61,7 +78,14 @@ describe('instance methods', () => { return command.wsk().then(() => { expect(RuntimeLib.init).toHaveBeenLastCalledWith( - { apihost: 'https://adobeioruntime.net', api_key: 1234, apiversion: 'v1' } + { + apihost: 'https://adobeioruntime.net', + api_key: 1234, + apiversion: 'v1', + auth_handler: expect.objectContaining({ + getAuthHeader: expect.any(Function) + }) + } ) }) }) @@ -93,7 +117,14 @@ describe('instance methods', () => { return command.wsk().then(() => { expect(RuntimeLib.init).toHaveBeenLastCalledWith( - { api_key: 1234, apihost: 'https://adobeioruntime.net', apiversion: 'v1' } + { + api_key: 1234, + apihost: 'https://adobeioruntime.net', + apiversion: 'v1', + auth_handler: expect.objectContaining({ + getAuthHeader: expect.any(Function) + }) + } ) delete process.env[PropertyEnv.APIHOST] }) @@ -106,7 +137,14 @@ 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', + auth_handler: expect.objectContaining({ + getAuthHeader: expect.any(Function) + }) + } ) delete process.env[PropertyEnv.APIHOST] }) @@ -123,7 +161,10 @@ describe('instance methods', () => { api_key: 'some-gibberish-not-a-real-key', namespace: 'some_namespace', apihost: value, - apiversion: 'v1' + apiversion: 'v1', + auth_handler: expect.objectContaining({ + getAuthHeader: expect.any(Function) + }) } ) delete process.env[PropertyEnv.APIHOST] @@ -141,7 +182,10 @@ describe('instance methods', () => { api_key: value, namespace: 'some_namespace', apihost: 'some.host', - apiversion: 'v1' + apiversion: 'v1', + auth_handler: expect.objectContaining({ + getAuthHeader: expect.any(Function) + }) } ) delete process.env[PropertyEnv.AUTH] @@ -159,7 +203,10 @@ describe('instance methods', () => { api_key: 'some-gibberish-not-a-real-key', namespace: 'some_namespace', apihost: 'some.host', - apiversion: value + apiversion: value, + auth_handler: expect.objectContaining({ + getAuthHeader: expect.any(Function) + }) } ) delete process.env[PropertyEnv.APIVERSION] @@ -312,4 +359,79 @@ describe('instance methods', () => { expect(command.error).toHaveBeenCalledWith('msg' + suffix) }) }) + + describe('authHandler', () => { + 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}`) + }) + }) }) diff --git a/test/RuntimeDeployCommand.test.js b/test/RuntimeDeployCommand.test.js deleted file mode 100644 index 532f7698..00000000 --- a/test/RuntimeDeployCommand.test.js +++ /dev/null @@ -1,437 +0,0 @@ -/* -Copyright 2024 Adobe Inc. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -*/ - -const TheCommand = require('../src/RuntimeDeployCommand.js') -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() -}) - -test('exports', async () => { - expect(typeof TheCommand).toEqual('function') - expect(TheCommand.prototype).toBeInstanceOf(Command) -}) - -test('description', async () => { - expect(TheCommand.description).not.toBeDefined() -}) - -test('aliases', async () => { - expect(TheCommand.aliases).toEqual([]) -}) - -test('flags', async () => { - expect(Object.keys(TheCommand.flags)).toEqual(expect.arrayContaining( - ['cert', 'key', 'apiversion', 'apihost', 'auth', 'insecure', 'debug', 'verbose', 'version', 'help', 'useragent'])) -}) - -test('args', async () => { - expect(TheCommand.args).toBeUndefined() -}) - -describe('instance methods', () => { - let command - - beforeEach(() => { - command = new TheCommand([]) - }) - - describe('init', () => { - test('is a function', async () => { - expect(command.init).toBeInstanceOf(Function) - }) - - test('apihost default', async () => { - const files = {} - files[require('path').join(require('os').homedir(), '.wskprops')] = 'AUTH=1234' - fakeFileSystem.addJson(files) - - return command.wsk().then(() => { - expect(RuntimeLib.init).toHaveBeenLastCalledWith( - { - apihost: 'https://adobeioruntime.net', - api_key: 1234, - apiversion: 'v1', - auth_handler: expect.objectContaining({ - getAuthHeader: expect.any(Function) - }) - } - ) - }) - }) - - test('empty APIHOST should throw', async () => { - process.env[PropertyEnv.APIHOST] = ' ' - await expect(command.wsk()).rejects.toThrow(new Error('An API host must be specified')) - }) - - test('null AUTH should throw', async () => { - delete process.env[PropertyEnv.APIHOST] - delete process.env[PropertyEnv.AUTH] - const files = {} - files[require('path').join(require('os').homedir(), '.wskprops')] = '' - fakeFileSystem.addJson(files) - await expect(command.wsk()).rejects.toThrow(new Error('An AUTH key must be specified')) - }) - - test('empty AUTH should throw', async () => { - process.env[PropertyEnv.AUTH] = ' ' - await expect(command.wsk()).rejects.toThrow(new Error('An AUTH key must be specified')) - delete process.env[PropertyEnv.AUTH] - }) - - test('not string AUTH should not throw', async () => { - const files = {} - files[require('path').join(require('os').homedir(), '.wskprops')] = 'AUTH=1234' - fakeFileSystem.addJson(files) - - return command.wsk().then(() => { - expect(RuntimeLib.init).toHaveBeenLastCalledWith( - { - api_key: 1234, - apihost: 'https://adobeioruntime.net', - apiversion: 'v1', - auth_handler: expect.objectContaining({ - getAuthHeader: expect.any(Function) - }) - } - ) - delete process.env[PropertyEnv.APIHOST] - }) - }) - - test('auth with inline comment should trim the comment', async () => { - const files = {} - files[require('path').join(require('os').homedir(), '.wskprops')] = 'AUTH=123 #inline-comment' - fakeFileSystem.addJson(files) - - return command.wsk().then(() => { - expect(RuntimeLib.init).toHaveBeenLastCalledWith( - { - api_key: 123, - apihost: 'https://adobeioruntime.net', - apiversion: 'v1', - auth_handler: expect.objectContaining({ - getAuthHeader: expect.any(Function) - }) - } - ) - delete process.env[PropertyEnv.APIHOST] - }) - }) - - test('apihost flag with env', async () => { - const value = 'http://my-server' - process.env[PropertyEnv.APIHOST] = value - - return command.wsk().then(() => { - expect(RuntimeLib.init).toHaveBeenLastCalledWith( - // the values are from the wsk.properties fixture - { - api_key: 'some-gibberish-not-a-real-key', - namespace: 'some_namespace', - apihost: value, - apiversion: 'v1', - auth_handler: expect.objectContaining({ - getAuthHeader: expect.any(Function) - }) - } - ) - delete process.env[PropertyEnv.APIHOST] - }) - }) - - test('auth flag with env', async () => { - const value = 'a9329ekasgk01' - process.env[PropertyEnv.AUTH] = value - - return command.wsk().then(() => { - expect(RuntimeLib.init).toHaveBeenLastCalledWith( - // the values are from the wsk.properties fixture - { - api_key: value, - namespace: 'some_namespace', - apihost: 'some.host', - apiversion: 'v1', - auth_handler: expect.objectContaining({ - getAuthHeader: expect.any(Function) - }) - } - ) - delete process.env[PropertyEnv.AUTH] - }) - }) - - test('apiversion flag with env', async () => { - const value = 'v2' - process.env[PropertyEnv.APIVERSION] = value - - return command.wsk().then(() => { - expect(RuntimeLib.init).toHaveBeenLastCalledWith( - // the values are from the wsk.properties fixture - { - api_key: 'some-gibberish-not-a-real-key', - namespace: 'some_namespace', - apihost: 'some.host', - apiversion: value, - auth_handler: expect.objectContaining({ - getAuthHeader: expect.any(Function) - }) - } - ) - delete process.env[PropertyEnv.APIVERSION] - }) - }) - - test('verbose flag', async () => { - const debug = require('debug') - const spy = jest.spyOn(debug, 'enable').mockReturnValue() - - command.argv = ['--verbose'] - return command.init().then(() => { - expect(spy).toHaveBeenLastCalledWith('*') - spy.mockClear() - }) - }) - - test('debug flag', async () => { - const debug = require('debug') - const spy = jest.spyOn(debug, 'enable').mockReturnValue() - - command.argv = ['--debug', 'foo,bar'] - return command.init().then(() => { - expect(spy).toHaveBeenLastCalledWith('foo,bar') - spy.mockClear() - }) - }) - - test('init no flag', async () => { - const debug = require('debug') - const spy = jest.spyOn(debug, 'enable').mockReturnValue() - - command.argv = [] - return command.init().then(() => { - expect(spy).not.toHaveBeenCalled() - spy.mockClear() - }) - }) - }) - - describe('ow', () => { - test('is a function', async () => { - expect(command.wsk).toBeInstanceOf(Function) - }) - - test('returns a promise', () => { - return command.wsk().then((ow) => { - expect(ow).toBe(ow) - }) - }) - - // eslint-disable-next-line jest/expect-expect - test('no config file, no problem', () => { - fakeFileSystem.clear() - process.env[PropertyEnv.AUTH] = '1234' - - return command.wsk().then(() => { - delete process.env[PropertyEnv.AUTH] - }) - }) - - // eslint-disable-next-line jest/expect-expect - test('should not throw if config file specified but doesnt exist', () => { - fakeFileSystem.clear() - process.env[PropertyEnv.CONFIG_FILE] = '/foo' - process.env[PropertyEnv.AUTH] = '1234' - - return command.wsk().then(() => { - delete process.env[PropertyEnv.CONFIG_FILE] - delete process.env[PropertyEnv.AUTH] - }) - }) - }) - - describe('handleError', () => { - const suffix = '\n specify --verbose flag for more information' - test('is a function', async () => { - expect(command.handleError).toBeInstanceOf(Function) - }) - - test('calls error', async () => { - command.error = jest.fn() - command.argv = ['--verbose'] - await command.handleError('msg', new Error('an error')) - expect(command.error).toHaveBeenCalledWith('msg: an error' + suffix) - }) - - test('optional error object', async () => { - command.error = jest.fn() - await command.handleError('msg') - expect(command.error).toHaveBeenCalledWith('msg') - }) - - test('with no arguments', async () => { - command.error = jest.fn() - await command.handleError() - expect(command.error).toHaveBeenCalledWith('unknown error') - }) - - test('openwhisk error', async () => { - command.error = jest.fn() - await command.handleError('msg', new OpenWhiskError('an error')) - expect(command.error).toHaveBeenCalledWith('msg: an error' + suffix) - }) - - test('openwhisk error, with internal error', async () => { - command.error = jest.fn() - const err = new Error('is') - err.error = 'The real error' - await command.handleError('msg', new OpenWhiskError('what', err)) - expect(command.error).toHaveBeenCalledWith('msg: the real error' + suffix) - }) - - test('openwhisk error, with internal error and statusCode', async () => { - command.error = jest.fn() - const err = new Error('is') - err.error = 'The real error' - await command.handleError('msg', new OpenWhiskError('what', err, 404)) - expect(command.error).toHaveBeenCalledWith('msg: the real error (404 Not Found)' + suffix) - }) - - test('openwhisk error, with internal error and code', async () => { - command.error = jest.fn() - const err = new Error('is') - err.error = 'The real error' - err.code = 12 - await command.handleError('msg', new OpenWhiskError('what', err)) - expect(command.error).toHaveBeenCalledWith('msg: the real error (12)' + suffix) - }) - - test('openwhisk error, with status code', async () => { - command.error = jest.fn() - const err = new Error('is') - err.error = 'The real error' - await command.handleError('msg', new OpenWhiskError('what', null, 401)) - expect(command.error).toHaveBeenCalledWith('msg: 401 Unauthorized' + suffix) - }) - - test('openwhisk error, with weird status code', async () => { - command.error = jest.fn() - const err = new Error('is') - err.error = 'The real error' - await command.handleError('msg', new OpenWhiskError('what', null, 999999)) - expect(command.error).toHaveBeenCalledWith('msg: 999999' + suffix) - }) - - test('with no message', async () => { - command.error = jest.fn() - await command.handleError('msg', new Error('')) - expect(command.error).toHaveBeenCalledWith('msg' + suffix) - }) - }) - - describe('authHandler', () => { - 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}`) - }) - }) -}) diff --git a/test/commands/runtime/rule/create.test.js b/test/commands/runtime/rule/create.test.js index 27c2ea02..46ff20bc 100644 --- a/test/commands/runtime/rule/create.test.js +++ b/test/commands/runtime/rule/create.test.js @@ -11,14 +11,14 @@ governing permissions and limitations under the License. */ const TheCommand = require('../../../../src/commands/runtime/rule/create.js') -const RuntimeDeployCommand = require('../../../../src/RuntimeDeployCommand.js') +const RuntimeBaseCommand = require('../../../../src/RuntimeBaseCommand.js') const rtAction = 'rules.create' const { stdout } = require('stdout-stderr') const RuntimeLib = require('@adobe/aio-lib-runtime') test('exports', async () => { expect(typeof TheCommand).toEqual('function') - expect(TheCommand.prototype instanceof RuntimeDeployCommand).toBeTruthy() + expect(TheCommand.prototype instanceof RuntimeBaseCommand).toBeTruthy() }) test('description', async () => { @@ -51,7 +51,7 @@ test('args', async () => { // eslint-disable-next-line jest/expect-expect test('base flags included in command flags', - createTestBaseFlagsFunction(TheCommand, RuntimeDeployCommand) + createTestBaseFlagsFunction(TheCommand, RuntimeBaseCommand) ) test('flags', async () => { diff --git a/test/commands/runtime/rule/delete.test.js b/test/commands/runtime/rule/delete.test.js index ebe62592..e266ca01 100644 --- a/test/commands/runtime/rule/delete.test.js +++ b/test/commands/runtime/rule/delete.test.js @@ -11,14 +11,14 @@ governing permissions and limitations under the License. */ const TheCommand = require('../../../../src/commands/runtime/rule/delete.js') -const RuntimeDeployCommand = require('../../../../src/RuntimeDeployCommand.js') +const RuntimeBaseCommand = require('../../../../src/RuntimeBaseCommand.js') const rtAction = 'rules.delete' const { stdout } = require('stdout-stderr') const RuntimeLib = require('@adobe/aio-lib-runtime') test('exports', async () => { expect(typeof TheCommand).toEqual('function') - expect(TheCommand.prototype instanceof RuntimeDeployCommand).toBeTruthy() + expect(TheCommand.prototype instanceof RuntimeBaseCommand).toBeTruthy() }) test('description', async () => { @@ -43,7 +43,7 @@ test('args', async () => { // eslint-disable-next-line jest/expect-expect test('base flags included in command flags', - createTestBaseFlagsFunction(TheCommand, RuntimeDeployCommand) + createTestBaseFlagsFunction(TheCommand, RuntimeBaseCommand) ) test('flags', async () => { diff --git a/test/commands/runtime/rule/update.test.js b/test/commands/runtime/rule/update.test.js index 26d9ed15..b91a6469 100644 --- a/test/commands/runtime/rule/update.test.js +++ b/test/commands/runtime/rule/update.test.js @@ -11,14 +11,14 @@ governing permissions and limitations under the License. */ const TheCommand = require('../../../../src/commands/runtime/rule/update.js') -const RuntimeDeployCommand = require('../../../../src/RuntimeDeployCommand.js') +const RuntimeBaseCommand = require('../../../../src/RuntimeBaseCommand.js') const rtAction = 'rules.update' const { stdout } = require('stdout-stderr') const RuntimeLib = require('@adobe/aio-lib-runtime') test('exports', async () => { expect(typeof TheCommand).toEqual('function') - expect(TheCommand.prototype instanceof RuntimeDeployCommand).toBeTruthy() + expect(TheCommand.prototype instanceof RuntimeBaseCommand).toBeTruthy() }) test('description', async () => { @@ -51,7 +51,7 @@ test('args', async () => { // eslint-disable-next-line jest/expect-expect test('base flags included in command flags', - createTestBaseFlagsFunction(TheCommand, RuntimeDeployCommand) + createTestBaseFlagsFunction(TheCommand, RuntimeBaseCommand) ) test('flags', async () => { From ce3aebb197f35682a51e2c7ae05f9922a31c8af5 Mon Sep 17 00:00:00 2001 From: Amulya Kashyap Date: Wed, 19 Feb 2025 16:13:26 +0530 Subject: [PATCH 5/8] Updated PR comments --- src/RuntimeBaseCommand.js | 2 +- src/properties.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/RuntimeBaseCommand.js b/src/RuntimeBaseCommand.js index 3236f06c..ff3cd677 100644 --- a/src/RuntimeBaseCommand.js +++ b/src/RuntimeBaseCommand.js @@ -79,7 +79,7 @@ class RuntimeBaseCommand extends Command { } options = await this.getOptions() options.auth_handler = authHandler - options.apihost = options.apihost ?? 'https://adobeioruntime.net' + options.apihost = options.apihost ?? PropertyDefault.DEPLOYSERVICEURL } return runtimeLib.init(options) } 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: '', From 8ac0a254f8b38d70a9c93ae2759253794eb32044 Mon Sep 17 00:00:00 2001 From: Amulya Kashyap Date: Wed, 19 Mar 2025 09:50:03 +0530 Subject: [PATCH 6/8] feat: ACNA-3602 - Added feature flag --- src/RuntimeBaseCommand.js | 6 +- test/RuntimeBaseCommand.test.js | 232 ++++++++++++++++++++------------ 2 files changed, 147 insertions(+), 91 deletions(-) diff --git a/src/RuntimeBaseCommand.js b/src/RuntimeBaseCommand.js index ff3cd677..5395022f 100644 --- a/src/RuntimeBaseCommand.js +++ b/src/RuntimeBaseCommand.js @@ -78,8 +78,10 @@ class RuntimeBaseCommand extends Command { } } options = await this.getOptions() - options.auth_handler = authHandler - options.apihost = options.apihost ?? PropertyDefault.DEPLOYSERVICEURL + 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/test/RuntimeBaseCommand.test.js b/test/RuntimeBaseCommand.test.js index f20972d0..208bad92 100644 --- a/test/RuntimeBaseCommand.test.js +++ b/test/RuntimeBaseCommand.test.js @@ -67,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) }) @@ -77,16 +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', - auth_handler: expect.objectContaining({ - getAuthHeader: expect.any(Function) - }) - } - ) + expect(RuntimeLib.init).toHaveBeenLastCalledWith(response) }) }) @@ -116,16 +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', - auth_handler: expect.objectContaining({ - getAuthHeader: expect.any(Function) - }) - } - ) + expect(RuntimeLib.init).toHaveBeenLastCalledWith(response) delete process.env[PropertyEnv.APIHOST] }) }) @@ -140,10 +127,7 @@ describe('instance methods', () => { { api_key: 123, apihost: 'https://adobeioruntime.net', - apiversion: 'v1', - auth_handler: expect.objectContaining({ - getAuthHeader: expect.any(Function) - }) + apiversion: 'v1' } ) delete process.env[PropertyEnv.APIHOST] @@ -161,10 +145,7 @@ describe('instance methods', () => { api_key: 'some-gibberish-not-a-real-key', namespace: 'some_namespace', apihost: value, - apiversion: 'v1', - auth_handler: expect.objectContaining({ - getAuthHeader: expect.any(Function) - }) + apiversion: 'v1' } ) delete process.env[PropertyEnv.APIHOST] @@ -182,10 +163,7 @@ describe('instance methods', () => { api_key: value, namespace: 'some_namespace', apihost: 'some.host', - apiversion: 'v1', - auth_handler: expect.objectContaining({ - getAuthHeader: expect.any(Function) - }) + apiversion: 'v1' } ) delete process.env[PropertyEnv.AUTH] @@ -203,10 +181,7 @@ describe('instance methods', () => { api_key: 'some-gibberish-not-a-real-key', namespace: 'some_namespace', apihost: 'some.host', - apiversion: value, - auth_handler: expect.objectContaining({ - getAuthHeader: expect.any(Function) - }) + apiversion: value } ) delete process.env[PropertyEnv.APIVERSION] @@ -361,77 +336,156 @@ describe('instance methods', () => { }) describe('authHandler', () => { - 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 + describe('when IS_DEPLOY_SERVICE_ENABLED = true', () => { + beforeEach(() => { + process.env.IS_DEPLOY_SERVICE_ENABLED = true }) - // Call wsk() which internally sets auth_handler - await command.wsk() + 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}`) + }) - // Ensure options were captured - expect(capturedOptions).toBeDefined() - expect(capturedOptions.auth_handler).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) - // Call getAuthHeader() from captured options - const authHeader = await capturedOptions.auth_handler.getAuthHeader() + const options = { + auth_handler: { + getAuthHeader: async () => `Bearer ${mockToken}` + }, + apihost: 'https://custom-api.adobe.com' + } - expect(context.setCli).toHaveBeenCalledWith({ 'cli.bare-output': true }, false) - expect(getCliEnv).toHaveBeenCalled() - expect(getToken).toHaveBeenCalled() - expect(authHeader).toBe(`Bearer ${mockToken}`) - }) + 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] - test('With Options : should return the correct Authorization header using getAuthHeader', async () => { - const mockToken = 'mock-access-token' - getToken.mockResolvedValue(mockToken) + const mockToken = 'mock-access-token' + getToken.mockResolvedValue(mockToken) - const options = { - auth_handler: { - getAuthHeader: async () => `Bearer ${mockToken}` - }, - apihost: 'https://custom-api.adobe.com' - } + command.getOptions = jest.fn().mockResolvedValue({}) - await command.wsk(options) // Call wsk() with an existing options object + // Mock runtimeLib.init to track its calls + const mockInit = jest.fn().mockResolvedValue({}) + RuntimeLib.init = mockInit - expect(RuntimeLib.init).toHaveBeenCalledWith(options) + // 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}`) + }) }) - test('Default OW Host testing', async () => { - delete process.env[PropertyEnv.APIHOST] + 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') + }) - const mockToken = 'mock-access-token' - getToken.mockResolvedValue(mockToken) + test('With Options : should return the correct Authorization header using getAuthHeader', async () => { + const mockToken = 'mock-access-token' + getToken.mockResolvedValue(mockToken) - command.getOptions = jest.fn().mockResolvedValue({}) + const options = { + auth_handler: { + getAuthHeader: async () => `Bearer ${mockToken}` + }, + apihost: 'https://custom-api.adobe.com' + } - // Mock runtimeLib.init to track its calls - const mockInit = jest.fn().mockResolvedValue({}) - RuntimeLib.init = mockInit + await command.wsk(options) // Call wsk() with an existing options object - // Call wsk() without options - await command.wsk() + expect(RuntimeLib.init).toHaveBeenCalledWith(options) + }) + + test('Default OW Host testing', async () => { + delete process.env[PropertyEnv.APIHOST] + + const mockToken = 'mock-access-token' + getToken.mockResolvedValue(mockToken) - // Assertions - expect(RuntimeLib.init).toHaveBeenCalled() + // command.getOptions = jest.fn().mockResolvedValue({}) - // 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') + // Mock runtimeLib.init to track its calls + const mockInit = jest.fn().mockResolvedValue({}) + RuntimeLib.init = mockInit - // Ensure the Authorization header is set correctly - expect(optionsPassedToInit.auth_handler).toBeDefined() - const authHeader = await optionsPassedToInit.auth_handler.getAuthHeader() - expect(authHeader).toBe(`Bearer ${mockToken}`) + // 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() + }) }) }) }) From ce76ece1a6e0f9fd714bd932f67908f057317768 Mon Sep 17 00:00:00 2001 From: Amulya Kashyap Date: Wed, 19 Mar 2025 17:20:24 +0530 Subject: [PATCH 7/8] removed overrides --- package.json | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a2c69c56..a1546fb2 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "@adobe/aio-lib-core-config": "^5", "@adobe/aio-lib-core-networking": "^5", "@adobe/aio-lib-env": "^3.0.1", - "@adobe/aio-lib-ims": "^7.0.2", + "@adobe/aio-lib-ims": "^8.0.1", "@adobe/aio-lib-runtime": "^7.0.2", "@oclif/core": "^1.3.0", "@types/jest": "^29.5.3", @@ -102,8 +102,5 @@ "setupFilesAfterEnv": [ "./test/jest.setup.js" ] - }, - "overrides": { - "@adobe/aio-lib-state": "5.1.0" } -} +} \ No newline at end of file From d6dfd85237757088a923f02de3d61aa10e3501e0 Mon Sep 17 00:00:00 2001 From: Amulya Kashyap Date: Fri, 21 Mar 2025 12:05:12 +0530 Subject: [PATCH 8/8] Updated aio-lib-runtime --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a1546fb2..17d51e3e 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "@adobe/aio-lib-core-networking": "^5", "@adobe/aio-lib-env": "^3.0.1", "@adobe/aio-lib-ims": "^8.0.1", - "@adobe/aio-lib-runtime": "^7.0.2", + "@adobe/aio-lib-runtime": "^7.1.0", "@oclif/core": "^1.3.0", "@types/jest": "^29.5.3", "chalk": "^4.1.2", @@ -103,4 +103,4 @@ "./test/jest.setup.js" ] } -} \ No newline at end of file +}