From 3fe366f43439d2be774d7f7b1d7f6eb5eb957815 Mon Sep 17 00:00:00 2001 From: John Boxall Date: Thu, 27 Jan 2022 15:54:32 -0800 Subject: [PATCH 01/13] Remove `slas:tenant:list` This endpoint is only available for internal service users. --- README.md | 1 - cli.js | 17 ----------------- lib/slas.js | 25 ------------------------- 3 files changed, 43 deletions(-) diff --git a/README.md b/README.md index e32194d0..a13d9759 100644 --- a/README.md +++ b/README.md @@ -334,7 +334,6 @@ Use `sfcc-ci --help` or just `sfcc-ci` to get started and see the full list of c user:create [options] Create a new user user:update [options] Update a user user:delete [options] Delete a user - slas:tenant:list [options] Lists all tenants that belong to a given organization slas:tenant:add [options] Adds a SLAS tenant to a given organization or updates an existing one slas:tenant:get [options] Gets a SLAS tenant from a given organization slas:tenant:delete [options] Deletes a SLAS tenant from a given organization diff --git a/cli.js b/cli.js index c3852f74..0174e098 100755 --- a/cli.js +++ b/cli.js @@ -1944,23 +1944,6 @@ program console.log(); }); - -program - .command('slas:tenant:list') - .description('Lists all tenants that belong to a given organization') - .option('--shortcode ', 'the organizations short code') - .option('-j, --json', 'Formats the output in json') - .action(async function(options) { - - var asJson = ( options.json ? options.json : false ); - - const slas = require('./lib/slas'); - await slas.cli.tenant.list(options.shortcode, asJson); - - }).on('--help', function() { - console.log(); - }); - program .command('slas:tenant:add') .description('Adds a SLAS tenant to a given organization or updates an existing one') diff --git a/lib/slas.js b/lib/slas.js index 607730d2..fb975efb 100644 --- a/lib/slas.js +++ b/lib/slas.js @@ -79,15 +79,6 @@ const slas = { handleCLIError('Could not get tenant: ', e.message, asJson) } }, - list: async (shortcode, asJson) => { - let result - try { - result = await slas.api.tenant.list(shortcode) - handleCLIOutput(result, asJson) - } catch (e) { - handleCLIError('Could not get tenants: ', e.message, asJson) - } - }, delete: async (tenantId, shortcode, asJson) => { let result try { @@ -197,22 +188,6 @@ const slas = { return await handleResponse(response); }, - list: async (shortcode) => { - const token = auth.getToken(); - - // set fallbacks - tenantId = secrets.getScapiTenantId(tenantId); - shortcode = secrets.getScapiShortCode(shortcode); - - const response = await fetch(getSlasUrl('', shortcode), { - method: 'GET', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } - }); - return await handleResponse(response); - }, delete: async (tenantId, shortcode) => { const token = auth.getToken(); From dd73e320026cd598039f55599b538ef9edb3f975 Mon Sep 17 00:00:00 2001 From: John Boxall Date: Thu, 27 Jan 2022 17:18:06 -0800 Subject: [PATCH 02/13] Fix `slas:tenant:add` - Fix up argument names --- cli.js | 23 +++++++++------------- lib/slas.js | 55 ++++++++++++++++++++++++++++++----------------------- 2 files changed, 40 insertions(+), 38 deletions(-) diff --git a/cli.js b/cli.js index 0174e098..5d35cd83 100755 --- a/cli.js +++ b/cli.js @@ -1946,24 +1946,19 @@ program program .command('slas:tenant:add') - .description('Adds a SLAS tenant to a given organization or updates an existing one') - .option('--tenant ', 'the tenant id used for slas') - .option('--shortcode ', 'the organizations short code') + .description('Add or update a SLAS tenant') + .option('--shortcode ', 'the organization\'s short code') + .option('--tenant ', 'the tenant id used') .option('--file ', 'JSON file with tenant details') - .option('--merchantname ', 'the name given for the tenant') + .option('--merchantname ', 'the name given for the tenant') .option('--tenantdescription ', 'the tenant descriptions') .option('--contact ', 'Contact person to manage tenants') .option('--email ', 'Email to contact') - .option('-j, --json', 'Formats the output in json') - .action(async function(options) { - - var asJson = ( options.json ? options.json : false ); - - const slas = require('./lib/slas'); - await slas.cli.tenant.add(options.tenant, options.shortcode, - options.tenantdescription, options.merchantname, options.contact, options.email, options.file, asJson); - - }).on('--help', function() { + .option('-j, --json', 'Formats the output in json', false) + .action(async (options) => { + await require('./lib/slas').cli.tenant.add(options) + }) + .on('--help', function() { console.log(); }); diff --git a/lib/slas.js b/lib/slas.js index fb975efb..4491acf2 100644 --- a/lib/slas.js +++ b/lib/slas.js @@ -1,9 +1,11 @@ const fetch = require('node-fetch'); const fs = require('fs'); +const jsonwebtoken = require('jsonwebtoken'); const auth = require('./auth'); const secrets = require('./secrets'); + /** * Generates a SLAS Admin Url * @param {string} tenantId the tenant found in BM - e.g bbsv_stg @@ -22,12 +24,13 @@ function getSlasUrl(tenantId, shortcode, clientId) { * @return {object} the parsed success response */ async function handleResponse(response) { - if (response.status > 299) { + if (!response.ok) { + console.error(await response.text()); throw new Error(`HTTP Fault ${response.status} (${response.statusText})`) } - const resultText = await response.text(); - return JSON.parse(resultText); + const data = await response.json(); + return data; } /** @@ -59,15 +62,14 @@ function handleCLIError(prefix, message, asJson) { const slas = { cli: { tenant : { - add: async (tenantId, shortcode, description, merchantName, contact, emailAddress, fileName, asJson) => { + add: async ({shortcode, tenant: instance, tenantdescription: description, merchantName, contact, emailAddress, file, json}) => { let result try { - result = await slas.api.tenant.add(tenantId, shortcode, - description, merchantName, contact, emailAddress, fileName) - console.info('sucessfully add tenant') - handleCLIOutput(result, asJson) + result = await slas.api.tenant.add({shortcode, instance, description, merchantName, contact, emailAddress, file}) + console.info('Successfully added tenant') + handleCLIOutput(result, json) } catch (e) { - handleCLIError('Could not add tenant: ', e.message, asJson) + handleCLIError('Could not add tenant: ', e.message, json) } }, get: async (tenantId, shortcode, asJson) => { @@ -137,38 +139,43 @@ const slas = { }, api: { tenant: { - add: async (tenantId, shortcode, description, merchantName, contact, emailAddress, fileName) => { + add: async ({shortcode, instance, description, merchantName, contact, emailAddress, file}) => { const token = auth.getToken(); - let params - // set fallbacks + const userEmail = jsonwebtoken.decode(token).sub + shortcode = secrets.getScapiShortCode(shortcode); - if (!fileName) { - tenantId = secrets.getScapiTenantId(tenantId); + + let body + if (!file) { + instance = secrets.getScapiTenantId(instance); description = description || `Added by SFCC-CI at ${(new Date()).toISOString()}` - merchantName = merchantName || tenantId - contact = contact || auth.getUser() - emailAddress = emailAddress || (auth.getUser() ? auth.getUser() : 'noreply@salesforce.com') + contact = contact || auth.getUser() || userEmail + emailAddress = emailAddress || auth.getUser() || userEmail - params = { - instance: tenantId, + body = { + instance, description, merchantName, contact, emailAddress } } else { - params = JSON.parse(fs.readFileSync(fileName, 'utf-8')); - tenantId = secrets.getScapiTenantId(tenantId || params.instance); + body = JSON.parse(fs.readFileSync(file, 'utf-8')); + console.log({body}) + instance = secrets.getScapiTenantId(instance || body.instance); } - const response = await fetch(getSlasUrl(tenantId, shortcode), { + + const url = getSlasUrl(instance, shortcode) + const options = { method: 'PUT', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, - body: JSON.stringify(params) - }); + body: JSON.stringify(body) + } + const response = await fetch(url, options); return await handleResponse(response); }, get: async (tenantId, shortcode) => { From c357f1589642cb5b9cdde2af9569e603ea758817 Mon Sep 17 00:00:00 2001 From: John Boxall Date: Thu, 27 Jan 2022 23:20:03 -0800 Subject: [PATCH 03/13] =?UTF-8?q?WIP=20=E2=80=93=C2=A0client=20list=20and?= =?UTF-8?q?=20get?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cli.js | 103 ++++++++++++++---------------------- lib/slas.js | 149 +++++++++++++++++++++++++--------------------------- 2 files changed, 111 insertions(+), 141 deletions(-) diff --git a/cli.js b/cli.js index 5d35cd83..53e8f96c 100755 --- a/cli.js +++ b/cli.js @@ -1946,63 +1946,51 @@ program program .command('slas:tenant:add') - .description('Add or update a SLAS tenant') - .option('--shortcode ', 'the organization\'s short code') - .option('--tenant ', 'the tenant id used') - .option('--file ', 'JSON file with tenant details') - .option('--merchantname ', 'the name given for the tenant') - .option('--tenantdescription ', 'the tenant descriptions') - .option('--contact ', 'Contact person to manage tenants') - .option('--email ', 'Email to contact') - .option('-j, --json', 'Formats the output in json', false) + .description('Add or update SLAS tenant') + .option('--shortcode ', 'Realm short code eg. `kv7kzm78`') + .option('--tenant ', 'Tenant ID eg. `zzrf_001`') + .option('--file ', 'Optional path to JSON file with tenant details') + .option('-j, --json', 'Format output in json', false) .action(async (options) => { - await require('./lib/slas').cli.tenant.add(options) + await require('./lib/slas').cli.tenant.add(options); }) - .on('--help', function() { + .on('--help', () => { console.log(); }); program .command('slas:tenant:get') - .description('Gets a SLAS tenant from a given organization') - .option('--tenant ', 'the tenant id used for slas') - .option('--shortcode ', 'the organizations short code') - .option('-j, --json', 'Formats the output in json') - .action(async function(options) { - - var asJson = ( options.json ? options.json : false ); - - const slas = require('./lib/slas'); - await slas.cli.tenant.get(options.tenant, options.shortcode, asJson); - - }).on('--help', function() { + .description('Retrive SLAS tenant') + .option('--shortcode ', 'Realm short code eg. `kv7kzm78`') + .option('--tenant ', 'Tenant ID eg. `zzrf_001`') + .option('-j, --json', 'Format output in json', false) + .action(async (options) => { + await require('./lib/slas').cli.tenant.get(options); + }) + .on('--help', () => { console.log(); }); program .command('slas:tenant:delete') - .description('Deletes a SLAS tenant from a given organization') - .option('--tenant ', 'the tenant id used for slas') - .option('--shortcode ', 'the organizations short code') - .option('-j, --json', 'Formats the output in json') - .action(async function(options) { - - var asJson = ( options.json ? options.json : false ); - - const slas = require('./lib/slas'); - await slas.cli.tenant.get(options.tenant, options.shortcode, asJson); - - }).on('--help', function() { + .description('Remove SLAS tenant') + .option('--shortcode ', 'Realm short code eg. `kv7kzm78`') + .option('--tenant ', 'Tenant ID eg. `zzrf_001`') + .option('-j, --json', 'Format output in json', false) + .action(async (options) => { + await require('./lib/slas').cli.tenant.delete(options); + }) + .on('--help', () => { console.log(); }); program .command('slas:client:add') - .description('Adds a SLAS client to a given tenant or updates an existing one') - .option('--tenant ', 'the tenant id used for slas') - .option('--shortcode ', 'the organizations short code') - .option('--file ', 'The JSON File used to set up the slas client') - .option('--clientid ', 'The client ID to add') + .description('Add or update SLAS client for a tenant') + .option('--shortcode ', 'Realm short code eg. `kv7kzm78`') + .option('--tenant ', 'Tenant ID eg. `zzrf_001`') + .option('--file ', 'Optional path to JSON file with client details.') + .option('--client ', 'Client ID, eg. `aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa') .option('--clientname ', 'The name of the client ID') .option('--privateclient ', 'true the client is private') .option('--ecomtenant ', 'the ecom tenant') @@ -2038,35 +2026,24 @@ program program .command('slas:client:get') .description('Gets a SLAS client from a given tenant') - .option('--tenant ', 'the tenant id used for slas') - .option('--shortcode ', 'the organizations short code') - .option('--clientid ', 'The client ID to get information for') - .option('-j, --json', 'Formats the output in json') - .action(async function(options) { - - var asJson = ( options.json ? options.json : false ); - - const slas = require('./lib/slas'); - await slas.cli.client.get(options.tenant, options.shortcode, options.clientid, asJson); - + .option('--shortcode ', 'Realm short code eg. `kv7kzm78`') + .option('--tenant ', 'Tenant ID eg. `zzrf_001`') + .option('--client ', 'Client ID, eg. `aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa') + .action(async (options) => { + await require('./lib/slas').cli.client.get(options); }).on('--help', function() { console.log(); }); program .command('slas:client:list') - .description('Lists all SLAS clients that belong to a given tenant') - .option('--tenant ', 'the tenant id used for slas') - .option('--shortcode ', 'the organizations short code') - .option('-j, --json', 'Formats the output in json') - .action(async function(options) { - - var asJson = ( options.json ? options.json : false ); - - const slas = require('./lib/slas'); - await slas.cli.client.list(options.tenant, options.shortcode, asJson); - - }).on('--help', function() { + .description('List SLAS clients for a tenant') + .option('--shortcode ', 'Realm short code eg. `kv7kzm78`') + .option('--tenant ', 'Tenant ID eg. `zzrf_001`') + .action(async (options) => { + await require('./lib/slas').cli.client.list(options); + }) + .on('--help', async () => { console.log(); }); diff --git a/lib/slas.js b/lib/slas.js index 4491acf2..0d810b14 100644 --- a/lib/slas.js +++ b/lib/slas.js @@ -13,8 +13,19 @@ const secrets = require('./secrets'); * @param {string} [clientId] if provided a client URL is generated */ function getSlasUrl(tenantId, shortcode, clientId) { - return `https://${shortcode}.api.commercecloud.salesforce.com/shopper/auth-admin/v1/tenants/${tenantId - + (clientId ? ('/clients/' + clientId) : '')}`; + const bits = [ + `https://${shortcode}.api.commercecloud.salesforce.com/shopper/auth-admin/v1/tenants` + ] + + if (tenantId) { + bits.push(tenantId); + } + + if (clientId) { + bits.push(`clients/${clientId}`); + } + + return bits.join('/') } @@ -24,13 +35,20 @@ function getSlasUrl(tenantId, shortcode, clientId) { * @return {object} the parsed success response */ async function handleResponse(response) { - if (!response.ok) { - console.error(await response.text()); - throw new Error(`HTTP Fault ${response.status} (${response.statusText})`) + if (response.ok) { + return await response.json(); } - const data = await response.json(); - return data; + const contentType = response.headers.get('content-type'); + const isJSON = contentType && contentType.includes('application/json') + let message; + if (isJSON) { + message = await response.json(); + } else { + message = await response.text(); + } + console.error(message); + throw new Error(`HTTP Fault ${response.status} (${response.statusText})`); } /** @@ -63,22 +81,20 @@ const slas = { cli: { tenant : { add: async ({shortcode, tenant: instance, tenantdescription: description, merchantName, contact, emailAddress, file, json}) => { - let result try { - result = await slas.api.tenant.add({shortcode, instance, description, merchantName, contact, emailAddress, file}) + const result = await slas.api.tenant.add({shortcode, instance, description, merchantName, contact, emailAddress, file}) console.info('Successfully added tenant') handleCLIOutput(result, json) } catch (e) { handleCLIError('Could not add tenant: ', e.message, json) } }, - get: async (tenantId, shortcode, asJson) => { - let result + get: async ({shortcode, tenant, json}) => { try { - result = await slas.api.tenant.get(tenantId, shortcode) - handleCLIOutput(result, asJson) + const result = await slas.api.tenant.get({shortcode, tenant}) + handleCLIOutput(result, json) } catch (e) { - handleCLIError('Could not get tenant: ', e.message, asJson) + handleCLIError('Could not get tenant: ', e.message, json) } }, delete: async (tenantId, shortcode, asJson) => { @@ -104,26 +120,20 @@ const slas = { handleCLIError('Could not add client: ', e.message, asJson) } }, - get: async (tenantId, shortcode, clientId, asJson) => { - let result + get: async ({shortcode, tenant, client}) => { try { - result = await slas.api.client.get(tenantId, shortcode, clientId) - handleCLIOutput(result, asJson) + const result = await slas.api.client.get({shortcode, tenant, client}) + console.dir(result) } catch (e) { - handleCLIError('Could not get tenant: ', e.message, asJson) + handleCLIError('Could not get client: ', e.message) } }, - list: async (shortcode, tenantId, asJson) => { - let result + list: async ({shortcode, tenant}) => { try { - result = await slas.api.client.list(shortcode, tenantId); - if (asJson) { - console.info(JSON.stringify(result, null, 4)); - } else { - result.data.forEach((element) => console.table(element)); - } + const result = await slas.api.client.list({shortcode, tenant}); + console.dir(result, {depth: null}); } catch (e) { - handleCLIError('Could not get tenants: ', e.message, asJson) + handleCLIError('Could not list clients: ', e.message) } }, delete: async (tenantId, shortcode, clientId, asJson) => { @@ -139,29 +149,21 @@ const slas = { }, api: { tenant: { - add: async ({shortcode, instance, description, merchantName, contact, emailAddress, file}) => { + add: async ({shortcode, instance, file}) => { const token = auth.getToken(); - const userEmail = jsonwebtoken.decode(token).sub - + shortcode = secrets.getScapiShortCode(shortcode); let body if (!file) { instance = secrets.getScapiTenantId(instance); - description = description || `Added by SFCC-CI at ${(new Date()).toISOString()}` - contact = contact || auth.getUser() || userEmail - emailAddress = emailAddress || auth.getUser() || userEmail - body = { instance, - description, - merchantName, - contact, - emailAddress + description: `Added by SFCC-CI at ${(new Date()).toISOString()}`, + emailAddress: jsonwebtoken.decode(token).sub } } else { body = JSON.parse(fs.readFileSync(file, 'utf-8')); - console.log({body}) instance = secrets.getScapiTenantId(instance || body.instance); } @@ -178,38 +180,34 @@ const slas = { const response = await fetch(url, options); return await handleResponse(response); }, - get: async (tenantId, shortcode) => { - const token = auth.getToken(); - - // set fallbacks - tenantId = secrets.getScapiTenantId(tenantId); + get: async ({shortcode, tenant}) => { shortcode = secrets.getScapiShortCode(shortcode); + tenant = secrets.getScapiTenantId(tenant); - const response = await fetch(getSlasUrl(tenantId, shortcode), { + const url = getSlasUrl(tenant, shortcode) + const options = { method: 'GET', headers: { - 'Authorization': `Bearer ${token}`, + 'Authorization': `Bearer ${auth.getToken()}`, 'Content-Type': 'application/json' } - }); - + } + const response = await fetch(url, options); return await handleResponse(response); }, - delete: async (tenantId, shortcode) => { - const token = auth.getToken(); - - // set fallbacks - tenantId = secrets.getScapiTenantId(tenantId); + delete: async ({shortcode, tenant}) => { shortcode = secrets.getScapiShortCode(shortcode); + tenant = secrets.getScapiTenantId(tenant); - const response = await fetch(getSlasUrl(tenantId, shortcode), { + const url = getSlasUrl(tenantId, shortcode); + const options = { method: 'DELETE', headers: { - 'Authorization': `Bearer ${token}`, + 'Authorization': `Bearer ${await auth.getToken()}`, 'Content-Type': 'application/json' } - }); - + } + const response = await fetch(url, options); return await handleResponse(response); }, }, @@ -248,37 +246,32 @@ const slas = { return await handleResponse(response); }, - get: async (tenantId, shortcode, clientId) => { - const token = auth.getToken(); + get: async ({shortcode, tenant, client}) => { + tenant = secrets.getScapiTenantId(tenant); + client = secrets.getScapiShortCode(client); - // set fallbacks - tenantId = secrets.getScapiTenantId(tenantId); - shortcode = secrets.getScapiShortCode(shortcode); - - const response = await fetch(getSlasUrl(tenantId, shortcode, clientId), { - method: 'GET', + const url = getSlasUrl(tenant, shortcode, client); + const options = { headers: { - 'Authorization': `Bearer ${token}`, + 'Authorization': `Bearer ${auth.getToken()}`, 'Content-Type': 'application/json' } - }); - + } + const response = await fetch(url, options); return await handleResponse(response); }, - list: async (shortcode, tenantId) => { - const token = auth.getToken(); - - // set fallbacks - tenantId = secrets.getScapiTenantId(tenantId); + list: async ({shortcode, tenant}) => { shortcode = secrets.getScapiShortCode(shortcode); + tenantId = secrets.getScapiTenantId(tenant); - const response = await fetch(getSlasUrl(tenantId, shortcode) + '/clients', { - method: 'GET', + const url = getSlasUrl(tenant, shortcode) + '/clients' + const options = { headers: { - 'Authorization': `Bearer ${token}`, + 'Authorization': `Bearer ${auth.getToken()}`, 'Content-Type': 'application/json' } - }); + } + const response = await fetch(url, options); return await handleResponse(response); }, delete: async (tenantId, shortcode, clientId) => { From 40c9de752e7f87900661afa7232bb783df8cdeca Mon Sep 17 00:00:00 2001 From: John Boxall Date: Fri, 28 Jan 2022 14:23:30 -0800 Subject: [PATCH 04/13] Update SLAS integration -Refactor options - Add help text - Update all client commands - Update API docs - Remove internal APIs --- README.md | 113 +++++++++++-------------------- cli.js | 132 +++++++++++++++++-------------------- lib/slas.js | 186 +++++++++++++++++++++------------------------------- 3 files changed, 171 insertions(+), 260 deletions(-) diff --git a/README.md b/README.md index a13d9759..d119ee23 100644 --- a/README.md +++ b/README.md @@ -276,7 +276,7 @@ You are now ready to use the tool by running the main command `sfcc-ci`. Use `sfcc-ci --help` or just `sfcc-ci` to get started and see the full list of commands available: -```bash +```txt Usage: cli [options] [command] Options: @@ -334,15 +334,13 @@ Use `sfcc-ci --help` or just `sfcc-ci` to get started and see the full list of c user:create [options] Create a new user user:update [options] Update a user user:delete [options] Delete a user - slas:tenant:add [options] Adds a SLAS tenant to a given organization or updates an existing one - slas:tenant:get [options] Gets a SLAS tenant from a given organization - slas:tenant:delete [options] Deletes a SLAS tenant from a given organization - slas:client:add [options] Adds a SLAS client to a given tenant or updates an existing one - slas:client:get [options] Gets a SLAS client from a given tenant - slas:client:list [options] Lists all SLAS clients that belong to a given tenant - slas:client:delete [options] Deletes a SLAS client from a given tenant - - Environment: + slas:tenant:add [options] Add or update SLAS a tenant. + slas:tenant:get [options] Get a SLAS tenant. + slas:client:add [options] Add or update a SLAS client for a tenant + slas:client:get [options] Get a SLAS client for a tenant. + slas:client:list [options] List SLAS clients for a tenant. + slas:client:delete [options] Delete a SLAS client for a tenant. + Environment: $SFCC_LOGIN_URL set login url used for authentication $SFCC_OAUTH_LOCAL_PORT set Oauth local port for authentication flow @@ -400,6 +398,8 @@ The use of environment variables is optional. `sfcc-ci` respects the following e * `SFCC_OAUTH_USER_PASSWORD` user password used for authentication * `SFCC_SANDBOX_API_HOST` set sandbox API host * `SFCC_SANDBOX_API_POLLING_TIMEOUT` set timeout for sandbox polling in minutes +* `SFCC_SCAPI_SHORTCODE` the Salesforce Commerce (Headless) API Shortcode +* `SFCC_SCAPI_TENANTID` the Salesforce Commerce (Headless) API TenantId * `DEBUG` enable verbose output If you only want a single CLI command to write debug messages prepend the command using, e.g. `DEBUG=* sfcc-ci `. @@ -977,107 +977,70 @@ callback | (Function) | Callback function executed as a result. The err APIs available in `require('sfcc').slas`: -`tenant.add(tenantId, shortcode, description, merchantName, contact, emailAddress, fileName)` +`tenant.add({shortcode, tenant, file})` -Adds the tenant details to the SLAS organization +Add or update SLAS a tenant. Param | Type | Description ------------- | ------------| -------------------------------- -tenantId | (String) | The tenant ID -shortcode | (String) | The short code of the org -description | (String) | Description of the tenant -merchantName | (String) | Name of the merchant -contact | (String) | Username of the user -emailAddress | (String) | Email address of the user -fileName | (String) | Path of the file containing all the params required for the tenant creation. +shortcode | (String) | Realm short code, `kv7kzm78` +tenant | (String) | Tenant ID, `zzrf_001` +file | (String) | Path to a JSON file with object details, `file.json` **Returns:** (Promise) The [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) with its `resolve` or `reject` methods called respectively with the `result` or the `error`. The `result` variable here is the newly created tenant reponse. *** -`tenant.get(tenantId, shortcode)` +`tenant.get({shortcode, tenant})` -Gets the tenant matching the given `tenantId` for the given `shortCode` +Get a SLAS tenant. Param | Type | Description ------------- | ------------| -------------------------------- -tenantId | (String) | The tenant ID -shortcode | (String) | The short code of the org +shortcode | (String) | Realm short code, `kv7kzm78` +tenant | (String) | Tenant ID, `zzrf_001` **Returns:** (Promise) The [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) with its `resolve` or `reject` methods called respectively with the `result` or the `error`. The `result` variable here is the tenant reponse. *** -`tenant.list(shortcode)` - -Lists all the tenants for the given `shortCode` - -Param | Type | Description -------------- | ------------| -------------------------------- -shortcode | (String) | The short code of the org - -**Returns:** (Promise) The [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) with its `resolve` or `reject` methods called respectively with the `result` or the `error`. The `result` variable here is list of tenants reponse. - -*** - -`tenant.delete(tenantId, shortcode)` - -Deletes the tenant matching the given `tenantId` for the given `shortCode` - -Param | Type | Description -------------- | ------------| -------------------------------- -tenantId | (String) | The tenant ID -shortcode | (String) | The short code of the org - -**Returns:** (Promise) The [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) with its `resolve` or `reject` methods called respectively with the `result` or the `error`. The `result` variable here is the deletion reponse. - -*** - -`client.add(tenantId, shortcode, file, clientid, clientname, privateclient, ecomtenant, ecomsite, secret, channels, scopes, redirecturis)` +`client.add({shortcode, tenant, client, body})` -Registers a new client within a given tenant +Add or update a SLAS client for a tenant Param | Type | Description ------------- | ------------| -------------------------------- -tenantId | (String) | The tenant ID -shortcode | (String) | The short code of the org -file | (String) | Path of the file containing all the params required for the client creation. -clientid | (String) | SLAS client id -clientname | (String) | The client name -privateclient | (Boolean) | Is the client a private client or not -ecomtenant | (String) | The ecom tenant -ecomsite | (String) | The ecom site -secret | (String) | The secret tied to the client ID -channels | (Array) | The list of channels for the client -scopes | (Array) | The list of scopes authorized for the client -redirecturis | (Array) | The list of redirect URIs authorized for the client +shortcode | (String) | Realm short code, `kv7kzm78` +tenant | (String) | Tenant ID, `zzrf_001` +client | (String) | Client ID, `aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa` +body | (Object) | Object with client's properties. **Returns:** (Promise) The [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) with its `resolve` or `reject` methods called respectively with the `result` or the `error`. The `result` variable here is the newly created client reponse. *** -`client.get(tenantId, shortcode, clientId)` +`client.get({shortcode, tenant, client})` -Gets the tenant matching the given `clientid` for the given `tenantId` and `shortCode` +Get a SLAS client for a tenant. Param | Type | Description ------------- | ------------| -------------------------------- -tenantId | (String) | The tenant ID -shortcode | (String) | The short code of the org -clientid | (String) | SLAS client id +shortcode | (String) | Realm short code, `kv7kzm78` +tenant | (String) | Tenant ID, `zzrf_001` +client | (String) | Client ID, `aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa` **Returns:** (Promise) The [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) with its `resolve` or `reject` methods called respectively with the `result` or the `error`. The `result` variable here is the client reponse. *** -`client.list(shortcode, tenantId)` +`client.list({shortcode, tenantId})` -Lists all the clients for the given `tenantId` and `shortCode` +List SLAS clients for a tenant. Param | Type | Description ------------- | ------------| -------------------------------- -shortcode | (String) | The short code of the org -tenantId | (String) | The tenant ID +shortcode | (String) | Realm short code, `kv7kzm78` +tenant | (String) | Tenant ID, `zzrf_001` **Returns:** (Promise) The [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) with its `resolve` or `reject` methods called respectively with the `result` or the `error`. The `result` variable here is list of clients reponse. @@ -1085,13 +1048,13 @@ tenantId | (String) | The tenant ID `client.delete(tenantId, shortcode, clientId)` -Deletes the client matching the given `clientId` for the given `tenantId` and `shortCode` +Delete a SLAS client for a tenant. Param | Type | Description ------------- | ------------| -------------------------------- -tenantId | (String) | The tenant ID -shortcode | (String) | The short code of the org -clientid | (String) | SLAS client id +shortcode | (String) | Realm short code, `kv7kzm78` +tenant | (String) | Tenant ID, `zzrf_001` +client | (String) | Client ID, `aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa` **Returns:** (Promise) The [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) with its `resolve` or `reject` methods called respectively with the `result` or the `error`. The `result` variable here is the deletion reponse. diff --git a/cli.js b/cli.js index 53e8f96c..6b8809e6 100755 --- a/cli.js +++ b/cli.js @@ -1944,125 +1944,113 @@ program console.log(); }); +const SLAS_OPTIONS = { + 'shortcode': ['--shortcode ', 'Realm short code, `kv7kzm78`'], + 'tenant': ['--tenant ', 'Tenant ID, `zzrf_001`'], + 'client': ['--client ', 'Client ID, `aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa`'], + 'file': ['--file ', 'Path to a JSON file with object details, `file.json`'], + 'json': ['-j, --json', 'Format output in json', false] +} + program .command('slas:tenant:add') - .description('Add or update SLAS tenant') - .option('--shortcode ', 'Realm short code eg. `kv7kzm78`') - .option('--tenant ', 'Tenant ID eg. `zzrf_001`') - .option('--file ', 'Optional path to JSON file with tenant details') - .option('-j, --json', 'Format output in json', false) + .description('Add or update SLAS a tenant.') + .option(...SLAS_OPTIONS.shortcode) + .option(...SLAS_OPTIONS.tenant) + .option(...SLAS_OPTIONS.file) + .option(...SLAS_OPTIONS.json) .action(async (options) => { await require('./lib/slas').cli.tenant.add(options); }) .on('--help', () => { console.log(); + console.log(' Examples:'); + console.log(); + console.log(' $ sfcc-ci slas:tenant:create --shortcode kv7kzm78 --tenant zzrf_001'); + console.log(' $ sfcc-ci slas:tenant:create --shortcode kv7kzm78 --tenant zzrf_001 --file tenant.json'); + console.log(); }); program .command('slas:tenant:get') - .description('Retrive SLAS tenant') - .option('--shortcode ', 'Realm short code eg. `kv7kzm78`') - .option('--tenant ', 'Tenant ID eg. `zzrf_001`') - .option('-j, --json', 'Format output in json', false) + .description('Get a SLAS tenant.') + .option(...SLAS_OPTIONS.shortcode) + .option(...SLAS_OPTIONS.tenant) + .option(...SLAS_OPTIONS.json) .action(async (options) => { await require('./lib/slas').cli.tenant.get(options); }) .on('--help', () => { console.log(); - }); - -program - .command('slas:tenant:delete') - .description('Remove SLAS tenant') - .option('--shortcode ', 'Realm short code eg. `kv7kzm78`') - .option('--tenant ', 'Tenant ID eg. `zzrf_001`') - .option('-j, --json', 'Format output in json', false) - .action(async (options) => { - await require('./lib/slas').cli.tenant.delete(options); - }) - .on('--help', () => { + console.log(' Examples:'); + console.log(); + console.log(' $ sfcc-ci slas:tenant:get --shortcode kv7kzm78 --tenant zzrf_001'); console.log(); }); program .command('slas:client:add') - .description('Add or update SLAS client for a tenant') - .option('--shortcode ', 'Realm short code eg. `kv7kzm78`') - .option('--tenant ', 'Tenant ID eg. `zzrf_001`') - .option('--file ', 'Optional path to JSON file with client details.') - .option('--client ', 'Client ID, eg. `aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa') - .option('--clientname ', 'The name of the client ID') - .option('--privateclient ', 'true the client is private') - .option('--ecomtenant ', 'the ecom tenant') - .option('--ecomsite ', 'the ecom site') - .option('--secret ', 'the slas secret, can be different then the secret in \ - account manager, but shouldnt be') - .option('--channels ', 'comma separated list of site IDs this API client should support') - .option('--scopes ', 'comma separated list of auth z scopes this API client should support') - .option('--redirecturis ', 'comma separated list of redirect uris this API client should support') - - .option('-j, --json', 'Formats the output in json') - .action(async function(options) { - - var asJson = ( options.json ? options.json : false ); - const clientid = options.clientid; - const clientname = options.clientname; - const privateclient = options.privateclient; - const ecomtenant = options.ecomtenant; - const ecomsite = options.ecomsite; - const secret = options.secret; - const channels = !options.channels || options.channels.split(',').map(item => item.trim()); - const scopes = !options.scopes || options.scopes.split(',').map(item => item.trim()); - const redirecturis = !options.redirecturis || options.redirecturis.split(',').map(item => item.trim()); - - const slas = require('./lib/slas'); - await slas.cli.client.add(options.tenant, options.shortcode, options.file, - clientid, clientname, privateclient, ecomtenant, ecomsite, secret, channels, scopes, redirecturis, asJson); - + .description('Add or update a SLAS client for a tenant.') + .option(...SLAS_OPTIONS.shortcode) + .option(...SLAS_OPTIONS.tenant) + .option(...SLAS_OPTIONS.client) + .option(...SLAS_OPTIONS.file) + .action(async (options) => { + await require('./lib/slas').cli.client.add(options); }).on('--help', function() { console.log(); + console.log(' Examples:'); + console.log(); + console.log(' $ sfcc-ci slas:client:add --shortcode kv7kzm78 --tenant zzrf_001 --client aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa --file client.json'); + console.log(); }); program .command('slas:client:get') - .description('Gets a SLAS client from a given tenant') - .option('--shortcode ', 'Realm short code eg. `kv7kzm78`') - .option('--tenant ', 'Tenant ID eg. `zzrf_001`') - .option('--client ', 'Client ID, eg. `aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa') + .description('Get a SLAS client for a tenant.') + .option(...SLAS_OPTIONS.shortcode) + .option(...SLAS_OPTIONS.tenant) + .option(...SLAS_OPTIONS.client) .action(async (options) => { await require('./lib/slas').cli.client.get(options); }).on('--help', function() { console.log(); + console.log(' Examples:'); + console.log(); + console.log(' $ sfcc-ci slas:client:get --shortcode kv7kzm78 --tenant zzrf_001 --client aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'); + console.log(); }); program .command('slas:client:list') - .description('List SLAS clients for a tenant') - .option('--shortcode ', 'Realm short code eg. `kv7kzm78`') - .option('--tenant ', 'Tenant ID eg. `zzrf_001`') + .description('List SLAS clients for a tenant.') + .option(...SLAS_OPTIONS.shortcode) + .option(...SLAS_OPTIONS.tenant) .action(async (options) => { await require('./lib/slas').cli.client.list(options); }) .on('--help', async () => { console.log(); + console.log(' Examples:'); + console.log(); + console.log(' $ sfcc-ci slas:client:list --shortcode kv7kzm78 --tenant zzrf_001'); + console.log(); }); program .command('slas:client:delete') - .description('Deletes a SLAS client from a given tenant') - .option('--tenant ', 'the tenant id used for slas') - .option('--shortcode ', 'the organizations short code') - .option('--clientid ', 'The Client ID to delete') - .option('-j, --json', 'Formats the output in json') + .description('Delete a SLAS client for a tenant.') + .option(...SLAS_OPTIONS.shortcode) + .option(...SLAS_OPTIONS.tenant) + .option(...SLAS_OPTIONS.client) .action(async function(options) { - - var asJson = ( options.json ? options.json : false ); - - const slas = require('./lib/slas'); - await slas.cli.client.get(options.tenant, options.shortcode, options.clientid, asJson); - + await require('./lib/slas').cli.client.delete(options); }).on('--help', function() { console.log(); + console.log(' Examples:'); + console.log(); + console.log(' $ sfcc-ci slas:client:delete --shortcode kv7kzm78 --tenant zzrf_001 --client aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'); + console.log(); }); program.on('--help', function() { diff --git a/lib/slas.js b/lib/slas.js index 0d810b14..3914ca6d 100644 --- a/lib/slas.js +++ b/lib/slas.js @@ -12,19 +12,10 @@ const secrets = require('./secrets'); * @param {string} shortcode the shortcode found in BM - e.g acdefg * @param {string} [clientId] if provided a client URL is generated */ -function getSlasUrl(tenantId, shortcode, clientId) { - const bits = [ - `https://${shortcode}.api.commercecloud.salesforce.com/shopper/auth-admin/v1/tenants` - ] - - if (tenantId) { - bits.push(tenantId); - } - - if (clientId) { - bits.push(`clients/${clientId}`); - } - +function getSlasUrl({shortcode, tenant, client}) { + const bits = [`https://${shortcode}.api.commercecloud.salesforce.com/shopper/auth-admin/v1/tenants`] + tenant && bits.push(tenant); + client && bits.push(`clients/${client}`); return bits.join('/') } @@ -76,13 +67,21 @@ function handleCLIError(prefix, message, asJson) { } } - +async function read(stream) { + const chunks = []; + for await (const chunk of stream) chunks.push(chunk); + return Buffer.concat(chunks).toString("utf8"); +} + const slas = { cli: { tenant : { - add: async ({shortcode, tenant: instance, tenantdescription: description, merchantName, contact, emailAddress, file, json}) => { + add: async ({shortcode, tenant, file, json}) => { + shortcode = secrets.getScapiShortCode(shortcode); + tenant = secrets.getScapiTenantId(tenant); + try { - const result = await slas.api.tenant.add({shortcode, instance, description, merchantName, contact, emailAddress, file}) + const result = await slas.api.tenant.add({shortcode, tenant, file}) console.info('Successfully added tenant') handleCLIOutput(result, json) } catch (e) { @@ -90,45 +89,54 @@ const slas = { } }, get: async ({shortcode, tenant, json}) => { + shortcode = secrets.getScapiShortCode(shortcode); + tenant = secrets.getScapiTenantId(tenant); + try { const result = await slas.api.tenant.get({shortcode, tenant}) handleCLIOutput(result, json) } catch (e) { handleCLIError('Could not get tenant: ', e.message, json) } - }, - delete: async (tenantId, shortcode, asJson) => { - let result - try { - result = await slas.api.tenant.delete(tenantId, shortcode) - handleCLIOutput(result, asJson) - } catch (e) { - handleCLIError('Could not delete tenant: ', e.message, asJson) - } } }, client : { - add: async (tenantId, shortcode, fileName,clientid, clientname, privateclient, - ecomtenant, ecomsite, secret, channels, scopes, redirecturis, asJson) => { - let result + add: async ({shortcode, tenant, client, file}) => { + if (!file) { + throw new Error('Option --file is required.'); + } + + shortcode = secrets.getScapiShortCode(shortcode); + tenant = secrets.getScapiTenantId(tenant); + + // Ensure `file` is valid JSON. + const body = JSON.parse(fs.readFileSync(file, 'utf-8')); + // Provided Client ID overrides JSON. + body.clientId = client + try { - result = await slas.api.client.add(tenantId, shortcode, fileName, clientid, clientname, - privateclient, ecomtenant, ecomsite, secret, channels, scopes, redirecturis); - console.info('sucessfully add client ') - handleCLIOutput(result, asJson) + const result = await slas.api.client.add({shortcode, tenant, client, body}); + console.info('Successfully added client') + handleCLIOutput(result, true) } catch (e) { - handleCLIError('Could not add client: ', e.message, asJson) + handleCLIError('Could not add client: ', e.message, true) } }, get: async ({shortcode, tenant, client}) => { + shortcode = secrets.getScapiShortCode(shortcode); + tenant = secrets.getScapiTenantId(tenant); + try { const result = await slas.api.client.get({shortcode, tenant, client}) - console.dir(result) + console.log(JSON.stringify(result, null, 4)) } catch (e) { handleCLIError('Could not get client: ', e.message) } }, list: async ({shortcode, tenant}) => { + shortcode = secrets.getScapiShortCode(shortcode); + tenantId = secrets.getScapiTenantId(tenant); + try { const result = await slas.api.client.list({shortcode, tenant}); console.dir(result, {depth: null}); @@ -136,38 +144,39 @@ const slas = { handleCLIError('Could not list clients: ', e.message) } }, - delete: async (tenantId, shortcode, clientId, asJson) => { - let result + delete: async ({shortcode, tenant, client}) => { + shortcode = secrets.getScapiShortCode(shortcode); + tenant = secrets.getScapiTenantId(tenant); + try { - result = await slas.api.client.delete(tenantId, shortcode, clientId) - handleCLIOutput(result, asJson) + const result = await slas.api.client.delete({shortcode, tenant, client}) + handleCLIOutput(result, true) } catch (e) { - handleCLIError('Could not delete tenant: ', e.message, asJson) + handleCLIError('Could not delete tenant: ', e.message, true) } } } }, api: { tenant: { - add: async ({shortcode, instance, file}) => { + add: async ({shortcode, tenant, file}) => { const token = auth.getToken(); - shortcode = secrets.getScapiShortCode(shortcode); - let body if (!file) { - instance = secrets.getScapiTenantId(instance); body = { - instance, + instance: tenant, description: `Added by SFCC-CI at ${(new Date()).toISOString()}`, - emailAddress: jsonwebtoken.decode(token).sub + emailAddress: jsonwebtoken.decode(token).sub, + merchantName: '_' } } else { body = JSON.parse(fs.readFileSync(file, 'utf-8')); - instance = secrets.getScapiTenantId(instance || body.instance); + // Provided `tenant` overrides `instance` from JSON. + body.instance = tenant } - const url = getSlasUrl(instance, shortcode) + const url = getSlasUrl({shortcode, tenant}) const options = { method: 'PUT', headers: { @@ -181,10 +190,7 @@ const slas = { return await handleResponse(response); }, get: async ({shortcode, tenant}) => { - shortcode = secrets.getScapiShortCode(shortcode); - tenant = secrets.getScapiTenantId(tenant); - - const url = getSlasUrl(tenant, shortcode) + const url = getSlasUrl({tenant, shortcode}) const options = { method: 'GET', headers: { @@ -195,62 +201,23 @@ const slas = { const response = await fetch(url, options); return await handleResponse(response); }, - delete: async ({shortcode, tenant}) => { - shortcode = secrets.getScapiShortCode(shortcode); - tenant = secrets.getScapiTenantId(tenant); - - const url = getSlasUrl(tenantId, shortcode); - const options = { - method: 'DELETE', - headers: { - 'Authorization': `Bearer ${await auth.getToken()}`, - 'Content-Type': 'application/json' - } - } - const response = await fetch(url, options); - return await handleResponse(response); - }, }, client: { - add: async (tenantId, shortcode, file, clientid, clientname, privateclient, - ecomtenant, ecomsite, secret, channels, scopes, redirecturis,) => { - const token = auth.getToken(); - - // set fallbacks - shortcode = secrets.getScapiShortCode(shortcode); - let params; - if (file) { - params = JSON.parse(fs.readFileSync(file, 'utf-8')); - } else { - params = { - cliendId: clientid, - name: clientname, - isPrivateClient: privateclient, - ecomTenant: ecomtenant, - ecomSite: ecomsite, - secret: secret, - channels: channels, - scopes: scopes, - redirectUri: redirecturis - } - } - tenantId = secrets.getScapiTenantId(tenantId || params.ecomTenant); - const response = await fetch(getSlasUrl(tenantId, shortcode, params.clientId), { + add: async ({shortcode, tenant, client, body}) => { + const url = getSlasUrl({shortcode, tenant, client}) + const options = { method: 'PUT', headers: { - 'Authorization': `Bearer ${token}`, + 'Authorization': `Bearer ${auth.getToken()}`, 'Content-Type': 'application/json' }, - body: JSON.stringify(params) - }); - + body: JSON.stringify(body) + } + const response = await fetch(url, options); return await handleResponse(response); }, get: async ({shortcode, tenant, client}) => { - tenant = secrets.getScapiTenantId(tenant); - client = secrets.getScapiShortCode(client); - - const url = getSlasUrl(tenant, shortcode, client); + const url = getSlasUrl({shortcode, tenant, client}); const options = { headers: { 'Authorization': `Bearer ${auth.getToken()}`, @@ -261,34 +228,27 @@ const slas = { return await handleResponse(response); }, list: async ({shortcode, tenant}) => { - shortcode = secrets.getScapiShortCode(shortcode); - tenantId = secrets.getScapiTenantId(tenant); - - const url = getSlasUrl(tenant, shortcode) + '/clients' + const url = getSlasUrl({tenant, shortcode}) + '/clients' const options = { headers: { 'Authorization': `Bearer ${auth.getToken()}`, 'Content-Type': 'application/json' } } + // TODO: If no clients belong to this tenant, SLAS returns a HTTP 404. const response = await fetch(url, options); return await handleResponse(response); }, - delete: async (tenantId, shortcode, clientId) => { - const token = auth.getToken(); - - // set fallbacks - tenantId = secrets.getScapiTenantId(tenantId); - shortcode = secrets.getScapiShortCode(shortcode); - - const response = await fetch(getSlasUrl(tenantId, shortcode, clientId), { + delete: async ({shortcode, tenant, client}) => { + const url = getSlasUrl({shortcode, tenant, client}); + const options = { method: 'DELETE', headers: { - 'Authorization': `Bearer ${token}`, + 'Authorization': `Bearer ${auth.getToken()}`, 'Content-Type': 'application/json' } - }); - + }; + const response = await fetch(url, options); return await handleResponse(response); }, } From 1dc5cdcba5d106d3c8a839e2f470caa372df6c55 Mon Sep 17 00:00:00 2001 From: John Boxall Date: Fri, 28 Jan 2022 14:57:23 -0800 Subject: [PATCH 05/13] Fix linting issues --- cli.js | 17 ++++++++++++++--- lib/slas.js | 6 ------ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/cli.js b/cli.js index 6b8809e6..c1d397c1 100755 --- a/cli.js +++ b/cli.js @@ -2001,7 +2001,11 @@ program console.log(); console.log(' Examples:'); console.log(); - console.log(' $ sfcc-ci slas:client:add --shortcode kv7kzm78 --tenant zzrf_001 --client aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa --file client.json'); + console.log(' $ sfcc-ci slas:client:add \\') + console.log(' --shortcode kv7kzm78 \\') + console.log(' --tenant zzrf_001 \\') + console.log(' --client aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa \\') + console.log(' --file client.json') console.log(); }); @@ -2017,7 +2021,10 @@ program console.log(); console.log(' Examples:'); console.log(); - console.log(' $ sfcc-ci slas:client:get --shortcode kv7kzm78 --tenant zzrf_001 --client aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'); + console.log(' $ sfcc-ci slas:client:get \\') + console.log(' --shortcode kv7kzm78 \\') + console.log(' --tenant zzrf_001 \\') + console.log(' --client aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa \\') console.log(); }); @@ -2049,7 +2056,11 @@ program console.log(); console.log(' Examples:'); console.log(); - console.log(' $ sfcc-ci slas:client:delete --shortcode kv7kzm78 --tenant zzrf_001 --client aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'); + console.log(' $ sfcc-ci slas:client:delete \\') + console.log(' --shortcode kv7kzm78 \\') + console.log(' --tenant zzrf_001 \\') + console.log(' --client aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa \\') + console.log(); }); diff --git a/lib/slas.js b/lib/slas.js index 3914ca6d..f248446f 100644 --- a/lib/slas.js +++ b/lib/slas.js @@ -66,12 +66,6 @@ function handleCLIError(prefix, message, asJson) { console.error(prefix + message) } } - -async function read(stream) { - const chunks = []; - for await (const chunk of stream) chunks.push(chunk); - return Buffer.concat(chunks).toString("utf8"); -} const slas = { cli: { From cbe226fde7252cfad4c90d4ea41adc1ef138782d Mon Sep 17 00:00:00 2001 From: John Boxall Date: Fri, 28 Jan 2022 15:01:03 -0800 Subject: [PATCH 06/13] Really fix linting. --- lib/slas.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/slas.js b/lib/slas.js index f248446f..08900153 100644 --- a/lib/slas.js +++ b/lib/slas.js @@ -66,7 +66,7 @@ function handleCLIError(prefix, message, asJson) { console.error(prefix + message) } } - + const slas = { cli: { tenant : { @@ -99,7 +99,7 @@ const slas = { if (!file) { throw new Error('Option --file is required.'); } - + shortcode = secrets.getScapiShortCode(shortcode); tenant = secrets.getScapiTenantId(tenant); From 4db82e703e513ad421f05a6f864b74dc686dc002 Mon Sep 17 00:00:00 2001 From: John Boxall Date: Thu, 3 Feb 2022 15:43:58 -0800 Subject: [PATCH 07/13] Adds SLAS unittests - Also throw an exception if the token available isn't a JWT. - And prettier format just to make a giant diff --- lib/slas.js | 239 +++++++++++++++++++++++++++------------------- test/unit/slas.js | 99 +++++++++++++++++++ 2 files changed, 238 insertions(+), 100 deletions(-) create mode 100644 test/unit/slas.js diff --git a/lib/slas.js b/lib/slas.js index 08900153..3f9a78f2 100644 --- a/lib/slas.js +++ b/lib/slas.js @@ -1,25 +1,36 @@ -const fetch = require('node-fetch'); -const fs = require('fs'); -const jsonwebtoken = require('jsonwebtoken'); +const fetch = require("node-fetch"); +const fs = require("fs"); +const jsonwebtoken = require("jsonwebtoken"); -const auth = require('./auth'); -const secrets = require('./secrets'); +const auth = require("./auth"); +const secrets = require("./secrets"); +function getAuthJWT() { + const token = auth.getToken(); + const data = jsonwebtoken.decode(token); + if (!data) { + throw new Error( + "Access Token is not a JWT. Check Account Manager API Client's Access Token Format is set to `JWT`." + ); + } + return token; +} /** * Generates a SLAS Admin Url * @param {string} tenantId the tenant found in BM - e.g bbsv_stg * @param {string} shortcode the shortcode found in BM - e.g acdefg - * @param {string} [clientId] if provided a client URL is generated + * @param {string} [clientId] if provided a client URL is generated */ -function getSlasUrl({shortcode, tenant, client}) { - const bits = [`https://${shortcode}.api.commercecloud.salesforce.com/shopper/auth-admin/v1/tenants`] +function getSlasUrl({ shortcode, tenant, client }) { + const bits = [ + `https://${shortcode}.api.commercecloud.salesforce.com/shopper/auth-admin/v1/tenants`, + ]; tenant && bits.push(tenant); client && bits.push(`clients/${client}`); - return bits.join('/') + return bits.join("/"); } - /** * Handles fetch response * @param {object} response the http client response @@ -30,8 +41,8 @@ async function handleResponse(response) { return await response.json(); } - const contentType = response.headers.get('content-type'); - const isJSON = contentType && contentType.includes('application/json') + const contentType = response.headers.get("content-type"); + const isJSON = contentType && contentType.includes("application/json"); let message; if (isJSON) { message = await response.json(); @@ -49,9 +60,9 @@ async function handleResponse(response) { */ function handleCLIOutput(result, asJson) { if (asJson) { - console.info(JSON.stringify(result, null, 4)) + console.info(JSON.stringify(result, null, 4)); } else { - console.table(result) + console.table(result); } } /** @@ -61,192 +72,220 @@ function handleCLIOutput(result, asJson) { */ function handleCLIError(prefix, message, asJson) { if (asJson) { - console.info(JSON.stringify({prefix, message}, null, 4)) + console.info(JSON.stringify({ prefix, message }, null, 4)); } else { - console.error(prefix + message) + console.error(prefix + message); } } const slas = { cli: { - tenant : { - add: async ({shortcode, tenant, file, json}) => { + tenant: { + add: async ({ shortcode, tenant, file, json }) => { shortcode = secrets.getScapiShortCode(shortcode); tenant = secrets.getScapiTenantId(tenant); try { - const result = await slas.api.tenant.add({shortcode, tenant, file}) - console.info('Successfully added tenant') - handleCLIOutput(result, json) + const result = await slas.api.tenant.add({ + shortcode, + tenant, + file, + }); + console.info("Successfully added tenant"); + handleCLIOutput(result, json); } catch (e) { - handleCLIError('Could not add tenant: ', e.message, json) + handleCLIError("Could not add tenant: ", e.message, json); } }, - get: async ({shortcode, tenant, json}) => { + get: async ({ shortcode, tenant, json }) => { shortcode = secrets.getScapiShortCode(shortcode); tenant = secrets.getScapiTenantId(tenant); try { - const result = await slas.api.tenant.get({shortcode, tenant}) - handleCLIOutput(result, json) + const result = await slas.api.tenant.get({ + shortcode, + tenant, + }); + handleCLIOutput(result, json); } catch (e) { - handleCLIError('Could not get tenant: ', e.message, json) + handleCLIError("Could not get tenant: ", e.message, json); } - } + }, }, - client : { - add: async ({shortcode, tenant, client, file}) => { + client: { + add: async ({ shortcode, tenant, client, file }) => { if (!file) { - throw new Error('Option --file is required.'); + throw new Error("Option --file is required."); } shortcode = secrets.getScapiShortCode(shortcode); tenant = secrets.getScapiTenantId(tenant); // Ensure `file` is valid JSON. - const body = JSON.parse(fs.readFileSync(file, 'utf-8')); + const body = JSON.parse(fs.readFileSync(file, "utf-8")); // Provided Client ID overrides JSON. - body.clientId = client + body.clientId = client; try { - const result = await slas.api.client.add({shortcode, tenant, client, body}); - console.info('Successfully added client') - handleCLIOutput(result, true) + const result = await slas.api.client.add({ + shortcode, + tenant, + client, + body, + }); + console.info("Successfully added client"); + handleCLIOutput(result, true); } catch (e) { - handleCLIError('Could not add client: ', e.message, true) + handleCLIError("Could not add client: ", e.message, true); } }, - get: async ({shortcode, tenant, client}) => { + get: async ({ shortcode, tenant, client }) => { shortcode = secrets.getScapiShortCode(shortcode); tenant = secrets.getScapiTenantId(tenant); try { - const result = await slas.api.client.get({shortcode, tenant, client}) - console.log(JSON.stringify(result, null, 4)) + const result = await slas.api.client.get({ + shortcode, + tenant, + client, + }); + console.log(JSON.stringify(result, null, 4)); } catch (e) { - handleCLIError('Could not get client: ', e.message) + handleCLIError("Could not get client: ", e.message); } }, - list: async ({shortcode, tenant}) => { + list: async ({ shortcode, tenant }) => { shortcode = secrets.getScapiShortCode(shortcode); tenantId = secrets.getScapiTenantId(tenant); try { - const result = await slas.api.client.list({shortcode, tenant}); - console.dir(result, {depth: null}); + const result = await slas.api.client.list({ + shortcode, + tenant, + }); + console.dir(result, { depth: null }); } catch (e) { - handleCLIError('Could not list clients: ', e.message) + handleCLIError("Could not list clients: ", e.message); } }, - delete: async ({shortcode, tenant, client}) => { + delete: async ({ shortcode, tenant, client }) => { shortcode = secrets.getScapiShortCode(shortcode); tenant = secrets.getScapiTenantId(tenant); try { - const result = await slas.api.client.delete({shortcode, tenant, client}) - handleCLIOutput(result, true) + const result = await slas.api.client.delete({ + shortcode, + tenant, + client, + }); + handleCLIOutput(result, true); } catch (e) { - handleCLIError('Could not delete tenant: ', e.message, true) + handleCLIError( + "Could not delete tenant: ", + e.message, + true + ); } - } - } + }, + }, }, api: { tenant: { - add: async ({shortcode, tenant, file}) => { - const token = auth.getToken(); + add: async ({ shortcode, tenant, file }) => { + const token = getAuthJWT(); - let body + let body; if (!file) { body = { instance: tenant, - description: `Added by SFCC-CI at ${(new Date()).toISOString()}`, + description: `Added by SFCC-CI at ${new Date().toISOString()}`, emailAddress: jsonwebtoken.decode(token).sub, - merchantName: '_' - } + merchantName: "_", + }; } else { - body = JSON.parse(fs.readFileSync(file, 'utf-8')); + body = JSON.parse(fs.readFileSync(file, "utf-8")); // Provided `tenant` overrides `instance` from JSON. - body.instance = tenant + body.instance = tenant; } - const url = getSlasUrl({shortcode, tenant}) + const url = getSlasUrl({ shortcode, tenant }); const options = { - method: 'PUT', + method: "PUT", headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", }, - body: JSON.stringify(body) - } + body: JSON.stringify(body), + }; const response = await fetch(url, options); return await handleResponse(response); }, - get: async ({shortcode, tenant}) => { - const url = getSlasUrl({tenant, shortcode}) + get: async ({ shortcode, tenant }) => { + const url = getSlasUrl({ tenant, shortcode }); const options = { - method: 'GET', + method: "GET", headers: { - 'Authorization': `Bearer ${auth.getToken()}`, - 'Content-Type': 'application/json' - } - } + Authorization: `Bearer ${getAuthJWT()}`, + "Content-Type": "application/json", + }, + }; const response = await fetch(url, options); return await handleResponse(response); }, }, client: { - add: async ({shortcode, tenant, client, body}) => { - const url = getSlasUrl({shortcode, tenant, client}) + add: async ({ shortcode, tenant, client, body }) => { + const url = getSlasUrl({ shortcode, tenant, client }); const options = { - method: 'PUT', + method: "PUT", headers: { - 'Authorization': `Bearer ${auth.getToken()}`, - 'Content-Type': 'application/json' + Authorization: `Bearer ${getAuthJWT()}`, + "Content-Type": "application/json", }, - body: JSON.stringify(body) - } + body: JSON.stringify(body), + }; const response = await fetch(url, options); return await handleResponse(response); }, - get: async ({shortcode, tenant, client}) => { - const url = getSlasUrl({shortcode, tenant, client}); + get: async ({ shortcode, tenant, client }) => { + const url = getSlasUrl({ shortcode, tenant, client }); const options = { headers: { - 'Authorization': `Bearer ${auth.getToken()}`, - 'Content-Type': 'application/json' - } - } + Authorization: `Bearer ${getAuthJWT()}`, + "Content-Type": "application/json", + }, + }; const response = await fetch(url, options); return await handleResponse(response); }, - list: async ({shortcode, tenant}) => { - const url = getSlasUrl({tenant, shortcode}) + '/clients' + list: async ({ shortcode, tenant }) => { + const url = getSlasUrl({ tenant, shortcode }) + "/clients"; const options = { headers: { - 'Authorization': `Bearer ${auth.getToken()}`, - 'Content-Type': 'application/json' - } - } + Authorization: `Bearer ${getAuthJWT()}`, + "Content-Type": "application/json", + }, + }; // TODO: If no clients belong to this tenant, SLAS returns a HTTP 404. const response = await fetch(url, options); return await handleResponse(response); }, - delete: async ({shortcode, tenant, client}) => { - const url = getSlasUrl({shortcode, tenant, client}); + delete: async ({ shortcode, tenant, client }) => { + const url = getSlasUrl({ shortcode, tenant, client }); const options = { - method: 'DELETE', + method: "DELETE", headers: { - 'Authorization': `Bearer ${auth.getToken()}`, - 'Content-Type': 'application/json' - } + Authorization: `Bearer ${getAuthJWT()}`, + "Content-Type": "application/json", + }, }; const response = await fetch(url, options); return await handleResponse(response); }, - } - } -} + }, + }, + getSlasUrl, +}; -module.exports = slas; \ No newline at end of file +module.exports = slas; diff --git a/test/unit/slas.js b/test/unit/slas.js new file mode 100644 index 00000000..e2f71254 --- /dev/null +++ b/test/unit/slas.js @@ -0,0 +1,99 @@ +const sinon = require("sinon"); +const proxyquire = require("proxyquire").noCallThru().noPreserveCache(); +const jsonwebtoken = require("jsonwebtoken"); +const { Response } = require("node-fetch"); + +const TENANT = { + shortcode: "aaaaaaa", + tenant: "aaaa_001", +}; +const CLIENT = Object.assign({client: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"}, TENANT); +const TOKEN = jsonwebtoken.sign( + { + sub: "user@example.com", + tenantFilter: "SLAS_ORGANIZATION_ADMIN:aaa_001", + roles: ["SLAS_ORGANIZATION_ADMIN"], + }, + "very-secret" +); + +const fetchStub = sinon.stub(); + +const slas = proxyquire("../../lib/slas", { + "node-fetch": fetchStub, + "./auth": { + getToken: () => TOKEN, + }, +}); + +describe("Shopper Login and API Access Service (SLAS)", () => { + afterEach(() => fetchStub.reset()); + + describe("Tenant CLI", () => { + it("Adds", async () => { + const response = { + contact: null, + description: "Added by SFCC-CI at 2022-01-01T12:00:00.000Z", + emailAddress: "user@example.com", + instance: "aaaa_001", + merchantName: "_", + phoneNo: null, + }; + + fetchStub.returns( + Promise.resolve( + new Response(JSON.stringify(response), { status: 200 }) + ) + ); + + await slas.cli.tenant.add(TENANT); + + sinon.assert.calledOnce(fetchStub); + sinon.assert.calledWithMatch( + fetchStub, + slas.getSlasUrl(TENANT), + sinon.match({ + body: sinon.match.string, + headers: { + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + method: "PUT", + }) + ); + }); + }); + + describe("Client CLI", () => { + it("Gets", async () => { + const response = { + clientId: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + name: "test", + scopes: "sfcc.shopper-categories", + redirectUri: "http://localhost:3000/callback", + channels: ["RefArch", "RefArchGlobal"], + isPrivateClient: false, + }; + + fetchStub.returns( + Promise.resolve( + new Response(JSON.stringify(response), { status: 200 }) + ) + ); + + await slas.cli.client.get(CLIENT); + + sinon.assert.calledOnce(fetchStub); + sinon.assert.calledWithMatch( + fetchStub, + slas.getSlasUrl(CLIENT), + sinon.match({ + headers: { + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + }) + ); + }); + }); +}); From 8a662ae54dde39222d2380ca668f1f1d13e2077d Mon Sep 17 00:00:00 2001 From: John Boxall Date: Thu, 3 Feb 2022 20:29:51 -0800 Subject: [PATCH 08/13] Test all the things --- lib/slas.js | 10 +++--- test/unit/slas.js | 91 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 96 insertions(+), 5 deletions(-) diff --git a/lib/slas.js b/lib/slas.js index 3f9a78f2..2273c4f5 100644 --- a/lib/slas.js +++ b/lib/slas.js @@ -1,3 +1,4 @@ +// SLAS: https://developer.salesforce.com/docs/commerce/commerce-api/references?meta=shopper-login-and-api-access-admin:Summary const fetch = require("node-fetch"); const fs = require("fs"); const jsonwebtoken = require("jsonwebtoken"); @@ -38,6 +39,9 @@ function getSlasUrl({ shortcode, tenant, client }) { */ async function handleResponse(response) { if (response.ok) { + if (response.status == 204) { + return true + } return await response.json(); } @@ -178,12 +182,11 @@ const slas = { tenant, client, }); - handleCLIOutput(result, true); + handleCLIOutput(result); } catch (e) { handleCLIError( "Could not delete tenant: ", - e.message, - true + e.message ); } }, @@ -224,7 +227,6 @@ const slas = { get: async ({ shortcode, tenant }) => { const url = getSlasUrl({ tenant, shortcode }); const options = { - method: "GET", headers: { Authorization: `Bearer ${getAuthJWT()}`, "Content-Type": "application/json", diff --git a/test/unit/slas.js b/test/unit/slas.js index e2f71254..fe38fb5a 100644 --- a/test/unit/slas.js +++ b/test/unit/slas.js @@ -7,7 +7,10 @@ const TENANT = { shortcode: "aaaaaaa", tenant: "aaaa_001", }; -const CLIENT = Object.assign({client: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"}, TENANT); +const CLIENT = Object.assign( + { client: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" }, + TENANT +); const TOKEN = jsonwebtoken.sign( { sub: "user@example.com", @@ -62,6 +65,36 @@ describe("Shopper Login and API Access Service (SLAS)", () => { }) ); }); + + it("Gets", async () => { + const response = { + contact: null, + description: "Added by SFCC-CI at 2022-01-01T12:00:00.000Z", + emailAddress: "user@example.com", + instance: "aaaa_001", + merchantName: "_", + phoneNo: null, + }; + + fetchStub.returns( + Promise.resolve( + new Response(JSON.stringify(response), { status: 200 }) + ) + ); + + await slas.cli.tenant.get(TENANT); + + sinon.assert.calledOnce(fetchStub); + sinon.assert.calledWithMatch( + fetchStub, + slas.getSlasUrl(TENANT), + sinon.match({ + headers: { + Authorization: `Bearer ${TOKEN}`, + }, + }) + ); + }); }); describe("Client CLI", () => { @@ -95,5 +128,61 @@ describe("Shopper Login and API Access Service (SLAS)", () => { }) ); }); + + it("Lists", async () => { + const response = { + data: [ + { + clientId: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + name: "test", + scopes: "sfcc.shopper-categories", + redirectUri: "http://localhost:3000/callback", + channels: ["RefArch", "RefArchGlobal"], + isPrivateClient: false, + }, + ], + }; + + fetchStub.returns( + Promise.resolve( + new Response(JSON.stringify(response), { status: 200 }) + ) + ); + + await slas.cli.client.list(TENANT); + + sinon.assert.calledOnce(fetchStub); + sinon.assert.calledWithMatch( + fetchStub, + slas.getSlasUrl(TENANT), + sinon.match({ + headers: { + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + }) + ); + }); + + it("Deletes", async () => { + fetchStub.returns( + Promise.resolve(new Response(null, { status: 204 })) + ); + + await slas.cli.client.delete(CLIENT); + + sinon.assert.calledOnce(fetchStub); + sinon.assert.calledWithMatch( + fetchStub, + slas.getSlasUrl(CLIENT), + sinon.match({ + headers: { + Authorization: `Bearer ${TOKEN}`, + "Content-Type": "application/json", + }, + method: "DELETE", + }) + ); + }); }); }); From 3ffff8f31dcf2ebdd01a4791332afc45c491362e Mon Sep 17 00:00:00 2001 From: John Boxall Date: Thu, 3 Feb 2022 21:40:49 -0800 Subject: [PATCH 09/13] Hopefully fix weird test. --- lib/slas.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/slas.js b/lib/slas.js index 2273c4f5..ac296058 100644 --- a/lib/slas.js +++ b/lib/slas.js @@ -40,7 +40,7 @@ function getSlasUrl({ shortcode, tenant, client }) { async function handleResponse(response) { if (response.ok) { if (response.status == 204) { - return true + return {success: true} } return await response.json(); } From 611f21f233f747aae7e5959d5137a9de8066e0ba Mon Sep 17 00:00:00 2001 From: John Boxall Date: Fri, 25 Feb 2022 14:37:13 -0800 Subject: [PATCH 10/13] Add credential quality command --- cli.js | 24 ++++++++++++++++++++- lib/slas.js | 60 ++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 78 insertions(+), 6 deletions(-) diff --git a/cli.js b/cli.js index c1d397c1..73be3126 100755 --- a/cli.js +++ b/cli.js @@ -1949,7 +1949,8 @@ const SLAS_OPTIONS = { 'tenant': ['--tenant ', 'Tenant ID, `zzrf_001`'], 'client': ['--client ', 'Client ID, `aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa`'], 'file': ['--file ', 'Path to a JSON file with object details, `file.json`'], - 'json': ['-j, --json', 'Format output in json', false] + 'json': ['-j, --json', 'Format output in json', false], + 'username': ['-u, --username ', 'Email address of user'] } program @@ -1989,6 +1990,27 @@ program }); program + .command('slas:tenant:credential-quality') + .description('Get credential quality metrics for a SLAS tenant.') + .option(...SLAS_OPTIONS.shortcode) + .option(...SLAS_OPTIONS.tenant) + .option(...SLAS_OPTIONS.username) + .action(async (options) => { + await require('./lib/slas').cli.tenant.credentialQuality(options); + }) + .on('--help', () => { + console.log(); + console.log(' Examples:'); + console.log(); + console.log(' $ sfcc-ci slas:tenant:credential-quality --shortcode kv7kzm78 --tenant zzrf_001'); + console.log(' $ sfcc-ci slas:tenant:credential-quality --shortcode kv7kzm78 --tenant zzrf_001 \\'); + console.log(' --shortcode kv7kzm78 \\') + console.log(' --tenant zzrf_001 \\') + console.log(' --username user@example.com \\') + console.log(); + }); + +program .command('slas:client:add') .description('Add or update a SLAS client for a tenant.') .option(...SLAS_OPTIONS.shortcode) diff --git a/lib/slas.js b/lib/slas.js index ac296058..61608f4c 100644 --- a/lib/slas.js +++ b/lib/slas.js @@ -40,7 +40,7 @@ function getSlasUrl({ shortcode, tenant, client }) { async function handleResponse(response) { if (response.ok) { if (response.status == 204) { - return {success: true} + return { success: true }; } return await response.json(); } @@ -115,6 +115,33 @@ const slas = { handleCLIError("Could not get tenant: ", e.message, json); } }, + credentialQuality: async ({ shortcode, tenant, username }) => { + shortcode = secrets.getScapiShortCode(shortcode); + tenant = secrets.getScapiTenantId(tenant); + + if (username) { + try { + const result = + await slas.api.tenant.userCredentialQuality({ + shortcode, + tenant, + username, + }); + console.dir(result); + } catch (e) {} + return; + } + + try { + const result = await slas.api.tenant.credentialQuality({ + shortcode, + tenant, + }); + console.dir(result); + } catch (e) { + handleCLIError("Could not get tenant: ", e.message); + } + }, }, client: { add: async ({ shortcode, tenant, client, file }) => { @@ -184,10 +211,7 @@ const slas = { }); handleCLIOutput(result); } catch (e) { - handleCLIError( - "Could not delete tenant: ", - e.message - ); + handleCLIError("Could not delete tenant: ", e.message); } }, }, @@ -235,6 +259,32 @@ const slas = { const response = await fetch(url, options); return await handleResponse(response); }, + credentialQuality: async ({ shortcode, tenant }) => { + const url = + getSlasUrl({ tenant, shortcode }) + "/cred-qual/login"; + const options = { + headers: { + Authorization: `Bearer ${getAuthJWT()}`, + "Content-Type": "application/json", + }, + }; + const response = await fetch(url, options); + return await handleResponse(response); + }, + userCredentialQuality: async ({ shortcode, tenant, username }) => { + const query = new URLSearchParams({ username }); + const url = + getSlasUrl({ tenant, shortcode }) + + `/cred-qual/user?${query}`; + const options = { + headers: { + Authorization: `Bearer ${getAuthJWT()}`, + "Content-Type": "application/json", + }, + }; + const response = await fetch(url, options); + return await handleResponse(response); + }, }, client: { add: async ({ shortcode, tenant, client, body }) => { From e7c6539cc95fe80b794ab776e03b98d3c00a91b4 Mon Sep 17 00:00:00 2001 From: John Boxall Date: Fri, 25 Feb 2022 14:43:12 -0800 Subject: [PATCH 11/13] Rename SLAS commands add -> create --- cli.js | 16 ++++++++-------- lib/slas.js | 22 +++++++++++----------- test/unit/slas.js | 8 ++++---- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/cli.js b/cli.js index 8c0c8c08..5e63f6cc 100755 --- a/cli.js +++ b/cli.js @@ -1969,14 +1969,14 @@ const SLAS_OPTIONS = { } program - .command('slas:tenant:add') - .description('Add or update SLAS a tenant.') + .command('slas:tenant:create') + .description('Create or update SLAS a tenant.') .option(...SLAS_OPTIONS.shortcode) .option(...SLAS_OPTIONS.tenant) .option(...SLAS_OPTIONS.file) .option(...SLAS_OPTIONS.json) .action(async (options) => { - await require('./lib/slas').cli.tenant.add(options); + await require('./lib/slas').cli.tenant.create(options); }) .on('--help', () => { console.log(); @@ -2025,20 +2025,20 @@ program console.log(); }); -program - .command('slas:client:add') - .description('Add or update a SLAS client for a tenant.') +program + .command('slas:client:create') + .description('Create or update a SLAS client for a tenant.') .option(...SLAS_OPTIONS.shortcode) .option(...SLAS_OPTIONS.tenant) .option(...SLAS_OPTIONS.client) .option(...SLAS_OPTIONS.file) .action(async (options) => { - await require('./lib/slas').cli.client.add(options); + await require('./lib/slas').cli.client.create(options); }).on('--help', function() { console.log(); console.log(' Examples:'); console.log(); - console.log(' $ sfcc-ci slas:client:add \\') + console.log(' $ sfcc-ci slas:client:create \\') console.log(' --shortcode kv7kzm78 \\') console.log(' --tenant zzrf_001 \\') console.log(' --client aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa \\') diff --git a/lib/slas.js b/lib/slas.js index 61608f4c..7e2902a8 100644 --- a/lib/slas.js +++ b/lib/slas.js @@ -85,20 +85,20 @@ function handleCLIError(prefix, message, asJson) { const slas = { cli: { tenant: { - add: async ({ shortcode, tenant, file, json }) => { + create: async ({ shortcode, tenant, file, json }) => { shortcode = secrets.getScapiShortCode(shortcode); tenant = secrets.getScapiTenantId(tenant); try { - const result = await slas.api.tenant.add({ + const result = await slas.api.tenant.create({ shortcode, tenant, file, }); - console.info("Successfully added tenant"); + console.info("Successfully created tenant"); handleCLIOutput(result, json); } catch (e) { - handleCLIError("Could not add tenant: ", e.message, json); + handleCLIError("Could not create tenant: ", e.message, json); } }, get: async ({ shortcode, tenant, json }) => { @@ -144,7 +144,7 @@ const slas = { }, }, client: { - add: async ({ shortcode, tenant, client, file }) => { + create: async ({ shortcode, tenant, client, file }) => { if (!file) { throw new Error("Option --file is required."); } @@ -158,16 +158,16 @@ const slas = { body.clientId = client; try { - const result = await slas.api.client.add({ + const result = await slas.api.client.create({ shortcode, tenant, client, body, }); - console.info("Successfully added client"); + console.info("Successfully created client"); handleCLIOutput(result, true); } catch (e) { - handleCLIError("Could not add client: ", e.message, true); + handleCLIError("Could not create client: ", e.message, true); } }, get: async ({ shortcode, tenant, client }) => { @@ -218,14 +218,14 @@ const slas = { }, api: { tenant: { - add: async ({ shortcode, tenant, file }) => { + create: async ({ shortcode, tenant, file }) => { const token = getAuthJWT(); let body; if (!file) { body = { instance: tenant, - description: `Added by SFCC-CI at ${new Date().toISOString()}`, + description: `Created by SFCC-CI at ${new Date().toISOString()}`, emailAddress: jsonwebtoken.decode(token).sub, merchantName: "_", }; @@ -287,7 +287,7 @@ const slas = { }, }, client: { - add: async ({ shortcode, tenant, client, body }) => { + create: async ({ shortcode, tenant, client, body }) => { const url = getSlasUrl({ shortcode, tenant, client }); const options = { method: "PUT", diff --git a/test/unit/slas.js b/test/unit/slas.js index fe38fb5a..b2fa9355 100644 --- a/test/unit/slas.js +++ b/test/unit/slas.js @@ -33,10 +33,10 @@ describe("Shopper Login and API Access Service (SLAS)", () => { afterEach(() => fetchStub.reset()); describe("Tenant CLI", () => { - it("Adds", async () => { + it("Creates", async () => { const response = { contact: null, - description: "Added by SFCC-CI at 2022-01-01T12:00:00.000Z", + description: "Created by SFCC-CI at 2022-01-01T12:00:00.000Z", emailAddress: "user@example.com", instance: "aaaa_001", merchantName: "_", @@ -49,7 +49,7 @@ describe("Shopper Login and API Access Service (SLAS)", () => { ) ); - await slas.cli.tenant.add(TENANT); + await slas.cli.tenant.create(TENANT); sinon.assert.calledOnce(fetchStub); sinon.assert.calledWithMatch( @@ -69,7 +69,7 @@ describe("Shopper Login and API Access Service (SLAS)", () => { it("Gets", async () => { const response = { contact: null, - description: "Added by SFCC-CI at 2022-01-01T12:00:00.000Z", + description: "Created by SFCC-CI at 2022-01-01T12:00:00.000Z", emailAddress: "user@example.com", instance: "aaaa_001", merchantName: "_", From 7e5bd60a493defcac55eec3426265433a0d930fc Mon Sep 17 00:00:00 2001 From: John Boxall Date: Fri, 25 Feb 2022 14:57:49 -0800 Subject: [PATCH 12/13] Consolidate client list command --- cli.js | 26 ++++++-------------------- lib/slas.js | 41 +++++++++++++++++++++++++---------------- test/unit/slas.js | 44 ++++++++++++++++++++++---------------------- 3 files changed, 53 insertions(+), 58 deletions(-) diff --git a/cli.js b/cli.js index 5e63f6cc..e42a1fa1 100755 --- a/cli.js +++ b/cli.js @@ -2047,40 +2047,26 @@ program }); program - .command('slas:client:get') - .description('Get a SLAS client for a tenant.') + .command('slas:client:list') + .description('List SLAS clients for a tenant.') .option(...SLAS_OPTIONS.shortcode) .option(...SLAS_OPTIONS.tenant) .option(...SLAS_OPTIONS.client) + .option(...SLAS_OPTIONS.json) .action(async (options) => { - await require('./lib/slas').cli.client.get(options); + await require('./lib/slas').cli.client.list(options); }).on('--help', function() { console.log(); console.log(' Examples:'); console.log(); - console.log(' $ sfcc-ci slas:client:get \\') + console.log(' $ sfcc-ci slas:client:list --shortcode kv7kzm78 --tenant zzrf_001'); + console.log(' $ sfcc-ci slas:client:list \\') console.log(' --shortcode kv7kzm78 \\') console.log(' --tenant zzrf_001 \\') console.log(' --client aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa \\') console.log(); }); -program - .command('slas:client:list') - .description('List SLAS clients for a tenant.') - .option(...SLAS_OPTIONS.shortcode) - .option(...SLAS_OPTIONS.tenant) - .action(async (options) => { - await require('./lib/slas').cli.client.list(options); - }) - .on('--help', async () => { - console.log(); - console.log(' Examples:'); - console.log(); - console.log(' $ sfcc-ci slas:client:list --shortcode kv7kzm78 --tenant zzrf_001'); - console.log(); - }); - program .command('slas:client:delete') .description('Delete a SLAS client for a tenant.') diff --git a/lib/slas.js b/lib/slas.js index 7e2902a8..850295e6 100644 --- a/lib/slas.js +++ b/lib/slas.js @@ -170,31 +170,40 @@ const slas = { handleCLIError("Could not create client: ", e.message, true); } }, - get: async ({ shortcode, tenant, client }) => { + list: async ({ shortcode, tenant, client, json }) => { shortcode = secrets.getScapiShortCode(shortcode); - tenant = secrets.getScapiTenantId(tenant); + tenantId = secrets.getScapiTenantId(tenant); - try { - const result = await slas.api.client.get({ - shortcode, - tenant, - client, - }); - console.log(JSON.stringify(result, null, 4)); - } catch (e) { - handleCLIError("Could not get client: ", e.message); + if (client) { + try { + const result = await slas.api.client.get({ + shortcode, + tenant, + client, + }); + + if (json) { + console.log(JSON.stringify(result, null, 4)); + } else { + console.dir(result) + } + return + } catch (e) { + return handleCLIError("Could not get client: ", e.message); + } } - }, - list: async ({ shortcode, tenant }) => { - shortcode = secrets.getScapiShortCode(shortcode); - tenantId = secrets.getScapiTenantId(tenant); try { const result = await slas.api.client.list({ shortcode, tenant, }); - console.dir(result, { depth: null }); + + if (json) { + console.log(JSON.stringify(result, null, 4)); + } else { + console.dir(result) + } } catch (e) { handleCLIError("Could not list clients: ", e.message); } diff --git a/test/unit/slas.js b/test/unit/slas.js index b2fa9355..be6022f8 100644 --- a/test/unit/slas.js +++ b/test/unit/slas.js @@ -98,14 +98,18 @@ describe("Shopper Login and API Access Service (SLAS)", () => { }); describe("Client CLI", () => { - it("Gets", async () => { + it("Lists", async () => { const response = { - clientId: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", - name: "test", - scopes: "sfcc.shopper-categories", - redirectUri: "http://localhost:3000/callback", - channels: ["RefArch", "RefArchGlobal"], - isPrivateClient: false, + data: [ + { + clientId: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + name: "test", + scopes: "sfcc.shopper-categories", + redirectUri: "http://localhost:3000/callback", + channels: ["RefArch", "RefArchGlobal"], + isPrivateClient: false, + }, + ], }; fetchStub.returns( @@ -114,12 +118,12 @@ describe("Shopper Login and API Access Service (SLAS)", () => { ) ); - await slas.cli.client.get(CLIENT); + await slas.cli.client.list(TENANT); sinon.assert.calledOnce(fetchStub); sinon.assert.calledWithMatch( fetchStub, - slas.getSlasUrl(CLIENT), + slas.getSlasUrl(TENANT), sinon.match({ headers: { Authorization: `Bearer ${TOKEN}`, @@ -129,18 +133,14 @@ describe("Shopper Login and API Access Service (SLAS)", () => { ); }); - it("Lists", async () => { + it("Lists one Client", async () => { const response = { - data: [ - { - clientId: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", - name: "test", - scopes: "sfcc.shopper-categories", - redirectUri: "http://localhost:3000/callback", - channels: ["RefArch", "RefArchGlobal"], - isPrivateClient: false, - }, - ], + clientId: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + name: "test", + scopes: "sfcc.shopper-categories", + redirectUri: "http://localhost:3000/callback", + channels: ["RefArch", "RefArchGlobal"], + isPrivateClient: false, }; fetchStub.returns( @@ -149,12 +149,12 @@ describe("Shopper Login and API Access Service (SLAS)", () => { ) ); - await slas.cli.client.list(TENANT); + await slas.cli.client.list(CLIENT); sinon.assert.calledOnce(fetchStub); sinon.assert.calledWithMatch( fetchStub, - slas.getSlasUrl(TENANT), + slas.getSlasUrl(CLIENT), sinon.match({ headers: { Authorization: `Bearer ${TOKEN}`, From 2a9473cf9f451fdd8874224cd57019ba1c0d3c29 Mon Sep 17 00:00:00 2001 From: John Boxall Date: Fri, 25 Feb 2022 15:07:13 -0800 Subject: [PATCH 13/13] ... --- lib/slas.js | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/lib/slas.js b/lib/slas.js index 850295e6..87de9905 100644 --- a/lib/slas.js +++ b/lib/slas.js @@ -98,7 +98,11 @@ const slas = { console.info("Successfully created tenant"); handleCLIOutput(result, json); } catch (e) { - handleCLIError("Could not create tenant: ", e.message, json); + handleCLIError( + "Could not create tenant: ", + e.message, + json + ); } }, get: async ({ shortcode, tenant, json }) => { @@ -167,7 +171,11 @@ const slas = { console.info("Successfully created client"); handleCLIOutput(result, true); } catch (e) { - handleCLIError("Could not create client: ", e.message, true); + handleCLIError( + "Could not create client: ", + e.message, + true + ); } }, list: async ({ shortcode, tenant, client, json }) => { @@ -185,11 +193,14 @@ const slas = { if (json) { console.log(JSON.stringify(result, null, 4)); } else { - console.dir(result) + console.dir(result); } - return + return; } catch (e) { - return handleCLIError("Could not get client: ", e.message); + return handleCLIError( + "Could not get client: ", + e.message + ); } } @@ -198,11 +209,11 @@ const slas = { shortcode, tenant, }); - + if (json) { console.log(JSON.stringify(result, null, 4)); } else { - console.dir(result) + console.dir(result); } } catch (e) { handleCLIError("Could not list clients: ", e.message);