diff --git a/package.json b/package.json index fcd62413..9069b80c 100644 --- a/package.json +++ b/package.json @@ -70,8 +70,9 @@ }, "repository": "adobe/aio-cli-plugin-runtime", "scripts": { - "eslint-fix": "eslint src test e2e --fix", - "posttest": "eslint src test e2e", + "lint-fix": "eslint src test e2e --fix", + "lint": "eslint src test e2e", + "posttest": "npm run lint", "test": "npm run unit-tests", "unit-tests": "jest --ci", "prepack": "oclif manifest && oclif readme --no-aliases", diff --git a/src/DeployServiceCommand.js b/src/DeployServiceCommand.js new file mode 100644 index 00000000..cdba96e4 --- /dev/null +++ b/src/DeployServiceCommand.js @@ -0,0 +1,97 @@ +/* +Copyright 2019 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 { Flags } = require('@oclif/core') + +const { PropertyDefault } = require('./properties') +const runtimeLib = require('@adobe/aio-lib-runtime') +const { getToken, context, CLI } = require('@adobe/aio-lib-ims') +const { getCliEnv } = require('@adobe/aio-lib-env') +const RuntimeBaseCommand = require('./RuntimeBaseCommand') + +class DeployServiceCommand extends RuntimeBaseCommand { +/** + * Retrieves an access token for Adobe I/O CLI authentication. + * This function handles both CLI and custom contexts, setting up the appropriate + * authentication context and retrieving the corresponding access token. + * + * @async + * @function getAccessToken + * @param {object} [options] - Options for token retrieval + * @param {string} [options.env] - The environment to use (e.g. 'prod', 'stage') + * @param {boolean} [options.useCachedToken] - Whether to use a cached token instead of requesting a new one + * @returns {Promise<{accessToken: string|null, env: string}>} An object containing: + * - accessToken: The retrieved access token for authentication, or null if token retrieval failed + * - env: The current CLI environment + * @throws {Error} If token retrieval fails or context setup fails + */ + async getAccessToken ({ env = getCliEnv(), useCachedToken = false } = {}) { + let contextName = CLI // default + const currentContext = await context.getCurrent() // potential override + + if (currentContext !== CLI) { + contextName = currentContext + } else { + await context.setCli({ 'cli.bare-output': true }, false) // set this globally + } + + let accessToken = null + if (useCachedToken) { + const contextConfig = await context.get(contextName) + accessToken = contextConfig?.access_token?.token + } else { + accessToken = await getToken(contextName) + } + + return { accessToken, env } + } + + getAuthHandler () { + const env = getCliEnv() + return { + getAuthHeader: async () => { + this.debug(`Retrieving CLI Token using env=${env}`) + const { accessToken } = await this.getAccessToken({ env }) + + return `Bearer ${accessToken}` + } + } + } + + async setRuntimeApiHostAndAuthHandler (options) { + let _options = structuredClone(options) + if (!_options?.['use-runtime-auth']) { + const endpoint = process.env.AIO_DEPLOY_SERVICE_URL ?? PropertyDefault.DEPLOYSERVICEURL + _options = _options ?? {} + _options.apihost = `${endpoint}/runtime` + _options.auth_handler = this.getAuthHandler() + } + + return _options + } + + async wsk (options) { + let _options = structuredClone(options) + if (!_options) { + _options = await super.getOptions() + _options = await this.setRuntimeApiHostAndAuthHandler(_options) + } + return runtimeLib.init(_options) + } +} + +DeployServiceCommand.flags = { + ...RuntimeBaseCommand.flags, + 'use-runtime-auth': Flags.boolean({ char: 'r', description: 'use Runtime auth [default: false]', default: false }) +} + +module.exports = DeployServiceCommand diff --git a/src/RuntimeBaseCommand.js b/src/RuntimeBaseCommand.js index 5395022f..70189ebd 100644 --- a/src/RuntimeBaseCommand.js +++ b/src/RuntimeBaseCommand.js @@ -18,9 +18,6 @@ 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 () { @@ -34,7 +31,8 @@ class RuntimeBaseCommand extends Command { apihost: flags.apihost || config.get('runtime.apihost') || properties.get('APIHOST') || PropertyDefault.APIHOST, namespace: config.get('runtime.namespace') || properties.get('NAMESPACE'), api_key: flags.auth || config.get('runtime.auth') || properties.get('AUTH'), - ignore_certs: flags.insecure || config.get('runtime.insecure') + ignore_certs: flags.insecure || config.get('runtime.insecure'), + 'use-runtime-auth': process.env.USE_RUNTIME_AUTH || flags['use-runtime-auth'] } // remove any null or undefined keys @@ -66,24 +64,11 @@ 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 - } + let _options = structuredClone(options) + if (!_options) { + _options = await this.getOptions() } - return runtimeLib.init(options) + return runtimeLib.init(_options) } getImsOrgId () { diff --git a/src/commands/runtime/action/create.js b/src/commands/runtime/action/create.js index 6991d6de..85e3d0b2 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 RuntimeBaseCommand = require('../../../RuntimeBaseCommand') +const DeployServiceCommand = require('../../../DeployServiceCommand') -class ActionCreate extends RuntimeBaseCommand { +class ActionCreate extends DeployServiceCommand { isUpdate () { return false } async run () { @@ -235,7 +235,8 @@ ActionCreate.args = [ ] ActionCreate.flags = { - ...RuntimeBaseCommand.flags, + ...DeployServiceCommand.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..4d69aad5 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 DeployServiceCommand = require('../../../DeployServiceCommand') const { Flags } = require('@oclif/core') -class ActionDelete extends RuntimeBaseCommand { +class ActionDelete extends DeployServiceCommand { async run () { const { flags, args } = await this.parse(ActionDelete) const name = args.actionName @@ -37,7 +37,7 @@ ActionDelete.args = [ ] ActionDelete.flags = { - ...RuntimeBaseCommand.flags, + ...DeployServiceCommand.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..4d8de262 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 DeployServiceCommand = require('../../../DeployServiceCommand') const { Flags } = require('@oclif/core') const fs = require('fs') -class ApiCreate extends RuntimeBaseCommand { +class ApiCreate extends DeployServiceCommand { async run () { const { args, flags } = await this.parse(ApiCreate) @@ -73,7 +73,7 @@ ApiCreate.args = [ ] ApiCreate.flags = { - ...RuntimeBaseCommand.flags, + ...DeployServiceCommand.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..df0acc91 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 DeployServiceCommand = require('../../../DeployServiceCommand') // eslint-disable-next-line no-unused-vars -class ApiDelete extends RuntimeBaseCommand { +class ApiDelete extends DeployServiceCommand { async run () { const { args } = await this.parse(ApiDelete) @@ -49,7 +49,7 @@ ApiDelete.args = [ ] ApiDelete.flags = { - ...RuntimeBaseCommand.flags + ...DeployServiceCommand.flags } ApiDelete.description = 'delete an API' diff --git a/src/commands/runtime/deploy/index.js b/src/commands/runtime/deploy/index.js index dcff4571..b2533abc 100644 --- a/src/commands/runtime/deploy/index.js +++ b/src/commands/runtime/deploy/index.js @@ -28,7 +28,7 @@ class IndexCommand extends RuntimeBaseCommand { const params = getKeyValueObjectFromMergedParameters(flags.param, flags['param-file']) const options = await this.getOptions() const entities = processPackage(packages, deploymentPackages, deploymentTriggers, params, false, options) - const ow = await this.wsk(options) + const ow = await this.wsk() const logger = this.log await deployPackage(entities, ow, logger.bind(this), this.getImsOrgId()) } catch (err) { diff --git a/src/commands/runtime/deploy/sync.js b/src/commands/runtime/deploy/sync.js index 437dffc5..a72912e5 100644 --- a/src/commands/runtime/deploy/sync.js +++ b/src/commands/runtime/deploy/sync.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 DeployServiceCommand = require('../../../DeployServiceCommand') const { setPaths, processPackage, syncProject } = require('@adobe/aio-lib-runtime').utils const { Flags } = require('@oclif/core') -class DeploySync extends RuntimeBaseCommand { +class DeploySync extends DeployServiceCommand { async run () { const { flags } = await this.parse(DeploySync) try { @@ -28,8 +28,9 @@ class DeploySync extends RuntimeBaseCommand { } const params = {} const options = await this.getOptions() + delete options['use-runtime-auth'] const entities = processPackage(packages, deploymentPackages, deploymentTriggers, params, false, options) - const ow = await this.wsk(options) + const ow = await this.wsk() const logger = this.log await syncProject(components.projectName, components.manifestPath, components.manifestContent, entities, ow, logger.bind(this), this.getImsOrgId()) } catch (err) { @@ -39,7 +40,7 @@ class DeploySync extends RuntimeBaseCommand { } DeploySync.flags = { - ...RuntimeBaseCommand.flags, + ...DeployServiceCommand.flags, manifest: Flags.string({ char: 'm', description: 'the manifest file location' // help description for flag diff --git a/src/commands/runtime/deploy/undeploy.js b/src/commands/runtime/deploy/undeploy.js index 5275a514..b1dd0e75 100644 --- a/src/commands/runtime/deploy/undeploy.js +++ b/src/commands/runtime/deploy/undeploy.js @@ -10,16 +10,17 @@ 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 DeployServiceCommand = require('../../../DeployServiceCommand') const { getProjectEntities, undeployPackage, processPackage, setPaths } = require('@adobe/aio-lib-runtime').utils const { Flags } = require('@oclif/core') -class DeployUndeploy extends RuntimeBaseCommand { +class DeployUndeploy extends DeployServiceCommand { async run () { const { flags } = await this.parse(DeployUndeploy) try { const options = await this.getOptions() - const ow = await this.wsk(options) + delete options['use-runtime-auth'] + const ow = await this.wsk() const logger = this.log let entities @@ -44,7 +45,7 @@ class DeployUndeploy extends RuntimeBaseCommand { } DeployUndeploy.flags = { - ...RuntimeBaseCommand.flags, + ...DeployServiceCommand.flags, manifest: Flags.string({ char: 'm', description: 'the manifest file location' // help description for flag diff --git a/src/commands/runtime/rule/create.js b/src/commands/runtime/rule/create.js index b01d4d47..e4b29152 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 DeployServiceCommand = require('../../../DeployServiceCommand') const { Flags } = require('@oclif/core') -class RuleCreate extends RuntimeBaseCommand { +class RuleCreate extends DeployServiceCommand { isUpdate () { return false } async run () { @@ -53,7 +53,7 @@ RuleCreate.args = [ ] RuleCreate.flags = { - ...RuntimeBaseCommand.flags, + ...DeployServiceCommand.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..bce60716 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 DeployServiceCommand = require('../../../DeployServiceCommand') const { Flags } = require('@oclif/core') -class RuleDelete extends RuntimeBaseCommand { +class RuleDelete extends DeployServiceCommand { async run () { const { flags, args } = await this.parse(RuleDelete) try { @@ -39,7 +39,7 @@ RuleDelete.args = [ ] RuleDelete.flags = { - ...RuntimeBaseCommand.flags, + ...DeployServiceCommand.flags, json: Flags.boolean({ description: 'output raw json' }) diff --git a/src/commands/runtime/rule/disable.js b/src/commands/runtime/rule/disable.js index 5f28ec40..ac23bb2f 100644 --- a/src/commands/runtime/rule/disable.js +++ b/src/commands/runtime/rule/disable.js @@ -9,9 +9,9 @@ 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 DeployServiceCommand = require('../../../DeployServiceCommand') -class RuleDisable extends RuntimeBaseCommand { +class RuleDisable extends DeployServiceCommand { async run () { const { args } = await this.parse(RuleDisable) try { @@ -36,7 +36,7 @@ RuleDisable.args = [ ] RuleDisable.flags = { - ...RuntimeBaseCommand.flags + ...DeployServiceCommand.flags } RuleDisable.aliases = [ diff --git a/src/commands/runtime/rule/enable.js b/src/commands/runtime/rule/enable.js index 916125ad..04d10955 100644 --- a/src/commands/runtime/rule/enable.js +++ b/src/commands/runtime/rule/enable.js @@ -9,9 +9,9 @@ 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 DeployServiceCommand = require('../../../DeployServiceCommand') -class RuleEnable extends RuntimeBaseCommand { +class RuleEnable extends DeployServiceCommand { async run () { const { args } = await this.parse(RuleEnable) try { @@ -36,7 +36,7 @@ RuleEnable.args = [ ] RuleEnable.flags = { - ...RuntimeBaseCommand.flags + ...DeployServiceCommand.flags } RuleEnable.aliases = [ diff --git a/src/commands/runtime/trigger/create.js b/src/commands/runtime/trigger/create.js index 16022071..5878a66a 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 DeployServiceCommand = require('../../../DeployServiceCommand') const { getKeyValueArrayFromMergedParameters } = require('@adobe/aio-lib-runtime').utils const { Flags } = require('@oclif/core') -class TriggerCreate extends RuntimeBaseCommand { +class TriggerCreate extends DeployServiceCommand { isUpdate () { return false } async run () { @@ -62,7 +62,7 @@ TriggerCreate.args = [ ] TriggerCreate.flags = { - ...RuntimeBaseCommand.flags, + ...DeployServiceCommand.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..39b22bdd 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 DeployServiceCommand = require('../../../DeployServiceCommand') const { parsePathPattern } = require('@adobe/aio-lib-runtime').utils -class TriggerDelete extends RuntimeBaseCommand { +class TriggerDelete extends DeployServiceCommand { async run () { const { args } = await this.parse(TriggerDelete) const triggerPath = args.triggerPath @@ -38,7 +38,7 @@ TriggerDelete.args = [ ] TriggerDelete.flags = { - ...RuntimeBaseCommand.flags + ...DeployServiceCommand.flags } TriggerDelete.description = 'Delete a trigger for Adobe I/O Runtime' diff --git a/src/properties.js b/src/properties.js index 40cd7964..613b83ce 100644 --- a/src/properties.js +++ b/src/properties.js @@ -35,7 +35,7 @@ const PropertyEnv = { const PropertyDefault = { AUTH: '', APIHOST: 'https://adobeioruntime.net', - DEPLOYSERVICEURL: 'https://adobeioruntime.net', + DEPLOYSERVICEURL: 'https://deploy-service.app-builder.adp.adobe.io', APIVERSION: 'v1', NAMESPACE: '_', CERT: '', diff --git a/test/DeployServiceCommand.test.js b/test/DeployServiceCommand.test.js new file mode 100644 index 00000000..0189204b --- /dev/null +++ b/test/DeployServiceCommand.test.js @@ -0,0 +1,178 @@ +/* +Copyright 2025 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/DeployServiceCommand.js') +const { Command } = require('@oclif/core') +const { PropertyDefault } = require('../src/properties') +const RuntimeLib = require('@adobe/aio-lib-runtime') +const { getToken, context, CLI } = require('@adobe/aio-lib-ims') +const { getCliEnv } = require('@adobe/aio-lib-env') + +jest.mock('@adobe/aio-lib-ims', () => ({ + getToken: jest.fn(), + context: { + getCurrent: jest.fn(), + setCli: jest.fn(), + get: jest.fn() + }, + CLI: 'cli' +})) + +jest.mock('@adobe/aio-lib-env', () => ({ + getCliEnv: jest.fn() +})) + +jest.mock('@adobe/aio-lib-runtime', () => ({ + init: jest.fn() +})) + +describe('DeployServiceCommand', () => { + let command + + beforeEach(() => { + command = new TheCommand([]) + jest.clearAllMocks() + }) + + test('exports', async () => { + expect(typeof TheCommand).toEqual('function') + expect(TheCommand.prototype).toBeInstanceOf(Command) + }) + + test('flags', async () => { + expect(Object.keys(TheCommand.flags)).toEqual(expect.arrayContaining([ + 'use-runtime-auth' + ])) + }) + + describe('getAccessToken', () => { + const mockToken = 'mock-token' + const mockEnv = 'prod' + + beforeEach(() => { + getCliEnv.mockReturnValue(mockEnv) + }) + + test('should use CLI context by default', async () => { + context.getCurrent.mockResolvedValue(CLI) + getToken.mockResolvedValue(mockToken) + + const result = await command.getAccessToken() + + expect(context.getCurrent).toHaveBeenCalled() + expect(context.setCli).toHaveBeenCalledWith({ 'cli.bare-output': true }, false) + expect(getToken).toHaveBeenCalledWith(CLI) + expect(result).toEqual({ + accessToken: mockToken, + env: mockEnv + }) + }) + + test('should use custom context when available', async () => { + const customContext = 'custom-context' + context.getCurrent.mockResolvedValue(customContext) + getToken.mockResolvedValue(mockToken) + + const result = await command.getAccessToken() + + expect(context.getCurrent).toHaveBeenCalled() + expect(context.setCli).not.toHaveBeenCalled() + expect(getToken).toHaveBeenCalledWith(customContext) + expect(result).toEqual({ + accessToken: mockToken, + env: mockEnv + }) + }) + + test('should use cached token when requested', async () => { + context.getCurrent.mockResolvedValue(CLI) + context.get.mockResolvedValue({ + access_token: { token: mockToken } + }) + + const result = await command.getAccessToken({ useCachedToken: true }) + + expect(context.get).toHaveBeenCalledWith(CLI) + expect(getToken).not.toHaveBeenCalled() + expect(result).toEqual({ + accessToken: mockToken, + env: mockEnv + }) + }) + }) + + describe('getAuthHandler', () => { + test('should return auth handler with correct header', async () => { + const mockToken = 'mock-token' + const mockEnv = 'prod' + getCliEnv.mockReturnValue(mockEnv) + context.getCurrent.mockResolvedValue(CLI) + getToken.mockResolvedValue(mockToken) + + const authHandler = command.getAuthHandler() + const header = await authHandler.getAuthHeader() + + expect(header).toBe(`Bearer ${mockToken}`) + }) + }) + + describe('setRuntimeApiHostAndAuthHandler', () => { + test('if options is not defined (set auth handler)', async () => { + const mockOptions = null + const result = await command.setRuntimeApiHostAndAuthHandler(mockOptions) + + expect(result.apihost).toBe(`${PropertyDefault.DEPLOYSERVICEURL}/runtime`) + expect(result.auth_handler).toBeDefined() + }) + + test('should set runtime API host and auth handler when use-runtime-auth is false', async () => { + const mockOptions = { someOption: 'value' } + const result = await command.setRuntimeApiHostAndAuthHandler(mockOptions) + + expect(result.apihost).toBe(`${PropertyDefault.DEPLOYSERVICEURL}/runtime`) + expect(result.auth_handler).toBeDefined() + }) + + test('should not modify options when use-runtime-auth is true', async () => { + const mockOptions = { 'use-runtime-auth': true, someOption: 'value' } + const result = await command.setRuntimeApiHostAndAuthHandler(mockOptions) + + expect(result).toEqual(mockOptions) + }) + + test('should use custom deploy service URL from environment', async () => { + const customUrl = 'https://custom-deploy-service.com' + process.env.AIO_DEPLOY_SERVICE_URL = customUrl + + const mockOptions = { someOption: 'value' } + const result = await command.setRuntimeApiHostAndAuthHandler(mockOptions) + + expect(result.apihost).toBe(`${customUrl}/runtime`) + delete process.env.AIO_DEPLOY_SERVICE_URL + }) + }) + + describe('wsk', () => { + test('should initialize runtime with correct options', async () => { + const mockOptions = { someOption: 'value' } + await command.wsk(mockOptions) + + expect(RuntimeLib.init).toHaveBeenCalled() + }) + + test('should get options from parent class when not provided', async () => { + await command.wsk() + + expect(RuntimeLib.init).toHaveBeenCalled() + }) + }) +}) diff --git a/test/RuntimeBaseCommand.test.js b/test/RuntimeBaseCommand.test.js index 208bad92..4bab89b0 100644 --- a/test/RuntimeBaseCommand.test.js +++ b/test/RuntimeBaseCommand.test.js @@ -15,8 +15,6 @@ 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(), @@ -223,13 +221,26 @@ describe('instance methods', () => { }) describe('ow', () => { + beforeEach(() => { + RuntimeLib.init.mockClear() + }) + test('is a function', async () => { expect(command.wsk).toBeInstanceOf(Function) }) test('returns a promise', () => { + RuntimeLib.init.mockReturnValue({}) return command.wsk().then((ow) => { - expect(ow).toBe(ow) + expect(ow).toBeDefined() + }) + }) + + test('returns a promise (pass options)', () => { + RuntimeLib.init.mockReturnValue({}) + const options = {} + return command.wsk(options).then((ow) => { + expect(ow).toBeDefined() }) }) @@ -334,158 +345,4 @@ 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() - }) - }) - }) }) diff --git a/test/commands/runtime/deploy/undeploy.test.js b/test/commands/runtime/deploy/undeploy.test.js index 1f1b55d8..cea598ad 100644 --- a/test/commands/runtime/deploy/undeploy.test.js +++ b/test/commands/runtime/deploy/undeploy.test.js @@ -41,7 +41,12 @@ test('flags', async () => { // some expected fake values const expectedEntities = { fake: 'entities' } const expectedEntitiesFromGet = { fakeGet: 'getentities' } -const expectedOWOptions = { api_key: 'some-gibberish-not-a-real-key', apihost: 'some.host', apiversion: 'v1', namespace: 'some_namespace' } +const expectedOWOptions = { + api_key: 'some-gibberish-not-a-real-key', + apihost: 'some.host', + apiversion: 'v1', + namespace: 'some_namespace' +} const expectedDepPackages = { fake: 'dep-packages' } const expectedDepTriggers = [{ fake: 'dep-triggers' }] const expectedPackages = { fake: 'packages' } @@ -75,7 +80,10 @@ describe('instance methods', () => { test('run with no flags', async () => { command.argv = [] await command.run() - expect(utils.setPaths).toHaveBeenCalledWith({ useragent: pkgNameVersion }) + expect(utils.setPaths).toHaveBeenCalledWith({ + 'use-runtime-auth': false, + useragent: pkgNameVersion + }) expect(utils.processPackage).toHaveBeenCalledWith(expectedPackages, {}, {}, {}, true, expectedOWOptions) expect(utils.getProjectEntities).not.toHaveBeenCalled() @@ -88,7 +96,11 @@ describe('instance methods', () => { test('run with manifest flag', async () => { command.argv = ['-m', 'fake-manifest.yml'] await command.run() - expect(utils.setPaths).toHaveBeenCalledWith({ manifest: 'fake-manifest.yml', useragent: pkgNameVersion }) + expect(utils.setPaths).toHaveBeenCalledWith({ + 'use-runtime-auth': false, + manifest: 'fake-manifest.yml', + useragent: pkgNameVersion + }) expect(utils.processPackage).toHaveBeenCalledWith(expectedPackages, {}, {}, {}, true, expectedOWOptions) expect(utils.getProjectEntities).not.toHaveBeenCalled()