diff --git a/e2e/Dockerfile b/e2e/Dockerfile index f5b7aa069..2a093d736 100644 --- a/e2e/Dockerfile +++ b/e2e/Dockerfile @@ -22,7 +22,7 @@ COPY e2e/*.yml /e2e COPY e2e/entrypoint.sh /tmp ADD e2e/cypress /e2e/cypress -RUN curl -v -L -O https://github.com/bcgov/gwa-cli/releases/download/v3.0.0/gwa_Linux_x86_64.tgz \ +RUN curl -v -L -O https://github.com/bcgov/gwa-cli/releases/download/v3.0.4/gwa_Linux_x86_64.tgz \ && tar -xzf gwa_Linux_x86_64.tgz \ && mv gwa /usr/local/bin/. diff --git a/e2e/cypress/pageObjects/products.ts b/e2e/cypress/pageObjects/products.ts index e1b1ff727..eab3c1d57 100644 --- a/e2e/cypress/pageObjects/products.ts +++ b/e2e/cypress/pageObjects/products.ts @@ -2,6 +2,7 @@ import { updateYamlDocument } from "@atomist/yaml-updater"; import _ = require("cypress/types/lodash"); const YAML = require('yamljs'); +const { kebabCase } = require('lodash'); class Products { path: string = '/manager/products' @@ -57,7 +58,7 @@ class Products { } editProduct(productName: string) { - const pname: string = productName.toLowerCase().replaceAll(' ', '-') + const pname: string = kebabCase(productName) cy.get(`[data-testid=${pname}-more-options-btn]`).first().click() cy.get(`[data-testid=${pname}-edit-btn]`).first().click() // cy.get(this.updateBtn).click() @@ -92,7 +93,7 @@ class Products { } editProductEnvironment(productName: string, envName: string) { - const pname: string = productName.toLowerCase().replaceAll(' ', '-') + const pname: string = kebabCase(productName) let env = this.getTestIdEnvName(envName); cy.get(`[data-testid=${pname}-${env}-edit-btn]`).click() cy.wait(2000) @@ -228,7 +229,7 @@ class Products { } deleteProductEnvironment(productName: string, envName: string) { - const pname: string = productName.toLowerCase().replaceAll(' ', '-') + const pname: string = kebabCase(productName) let env = this.getTestIdEnvName(envName); cy.get(`[data-testid=${pname}-${env}-more-options-btn]`).click() cy.get(`[data-testid=${pname}-${env}-delete-btn]`).click() @@ -236,8 +237,7 @@ class Products { } deleteProduct(productName: string) { - // this.editProduct(productName) - const pname: string = productName.toLowerCase().replaceAll(' ', '-') + const pname: string = kebabCase(productName) cy.get(`[data-testid=${pname}-edit-btn]`).first().click({ force: true }) cy.get(`[data-testid=${pname}-delete-btn]`).first().click({ force: true }) cy.get(this.deleteProductConfirmationBtn).click() diff --git a/e2e/cypress/tests/16-gwa-cli/01-cli-commands.ts b/e2e/cypress/tests/16-gwa-cli/01-cli-commands.ts index 6112a17a2..0b9b3d869 100644 --- a/e2e/cypress/tests/16-gwa-cli/01-cli-commands.ts +++ b/e2e/cypress/tests/16-gwa-cli/01-cli-commands.ts @@ -1,22 +1,11 @@ -import LoginPage from '../../pageObjects/login' -import ApplicationPage from '../../pageObjects/applications' -import ApiDirectoryPage from '../../pageObjects/apiDirectory' -import MyAccessPage from '../../pageObjects/myAccess' -const YAML = require('yamljs'); -let userSession: any let cli = require("../../fixtures/test_data/gwa-cli.json") +let userSession: any var cleanedUrl = Cypress.env('BASE_URL').replace(/^http?:\/\//i, ""); -const jose = require('node-jose') -describe('Verify CLI commands', () => { - const login = new LoginPage() - const apiDir = new ApiDirectoryPage() - const app = new ApplicationPage() - const ma = new MyAccessPage() +describe('Verify config / login CLI commands', () => { let namespace: string before(() => { - // cy.visit('/') cy.deleteAllCookies() cy.reload(true) }) @@ -25,7 +14,6 @@ describe('Verify CLI commands', () => { cy.preserveCookies() cy.fixture('apiowner').as('apiowner') cy.fixture('common-testdata').as('common-testdata') - // cy.visit(login.path) }) it('authenticates Janis (api owner) to get the user session token', () => { @@ -90,7 +78,6 @@ describe('Verify CLI commands', () => { }); }) - it('Check gwa gateway list command and verify the created namespace in the list', () => { cy.executeCliCommand('gwa gateway list --host ' + cleanedUrl + ' --scheme http').then((response) => { expect(response.stdout).to.contain(namespace); @@ -103,4 +90,4 @@ describe('Verify CLI commands', () => { cy.deleteAllCookies() }) -}) \ No newline at end of file +}) diff --git a/e2e/cypress/tests/16-gwa-cli/02-cli-generate-config-quick-start.ts b/e2e/cypress/tests/16-gwa-cli/02-cli-generate-config-quick-start.ts new file mode 100644 index 000000000..403087a01 --- /dev/null +++ b/e2e/cypress/tests/16-gwa-cli/02-cli-generate-config-quick-start.ts @@ -0,0 +1,115 @@ +import LoginPage from '../../pageObjects/login' +import ApplicationPage from '../../pageObjects/applications' +import ApiDirectoryPage from '../../pageObjects/apiDirectory' +import MyAccessPage from '../../pageObjects/myAccess' +import HomePage from '../../pageObjects/home'; +import Products from '../../pageObjects/products'; +const YAML = require('yamljs'); +let cli = require("../../fixtures/test_data/gwa-cli.json") +const { v4: uuidv4 } = require('uuid') +const jose = require('node-jose') + +let userSession: any +const customId = uuidv4().replace(/-/g, '').toLowerCase().substring(0, 3) +const serviceName = 'my-service-' + customId + +describe('Verify CLI commands for generate/apply config', () => { + const login = new LoginPage() + const apiDir = new ApiDirectoryPage() + const app = new ApplicationPage() + const ma = new MyAccessPage() + const pd = new Products() + let namespace: string + const home = new HomePage() + + before(() => { + // cy.visit('/') + cy.reload(true) + }) + + beforeEach(() => { + cy.preserveCookies() + cy.fixture('apiowner').as('apiowner') + // cy.visit(login.path) + }) + + it('authenticates Janis (api owner) to get the user session token', () => { + cy.get('@apiowner').then(({ apiTest }: any) => { + cy.getUserSessionTokenValue(apiTest.namespace, false).then((value) => { + userSession = value + }) + }) + }) + + it('Check gwa config command to set token', () => { + cy.executeCliCommand('gwa config set --token ' + userSession).then((response) => { + expect(response.stdout).to.contain("Config settings saved") + }); + }) + + it('Check gwa command to generate config for client credential template', () => { + const command = [ + 'gwa generate-config --template quick-start', + `--service ${serviceName}`, + '--upstream https://httpbin.org', + '--org ministry-of-health', + '--org-unit planning-and-innovation-division', + '--out gw-config-qs.yaml' + ].join(' '); + cy.executeCliCommand(command).then((response) => { + expect(response.stdout).to.contain("File gw-config-qs.yaml created") + }); + }) + + it('Check gwa command to apply generated config', () => { + cy.executeCliCommand('gwa apply -i gw-config-qs.yaml').then((response) => { + expect(response.stdout).to.contain("3/3 Published, 0 Skipped") + let wordOccurrences = (response.stdout.match(/\bcreated\b/g) || []).length; + expect(wordOccurrences).to.equal(2) + }); + }) + + it('Check gwa status --hosts include routes', () => { + cy.executeCliCommand('gwa status --hosts').then((response) => { + expect(response.stdout).to.contain('https://' + serviceName + '.dev.api.gov.bc.ca') + }); + }) + + it('activates namespace in Portal', () => { + cy.executeCliCommand('gwa gateway current').then((response) => { + const namespace = response.stdout.match(/\bgw-\w+/g)[0] + cy.activateGateway(namespace) + }) + }) + + it('Verify that the product created through gwa command is displayed in the portal', () => { + cy.visit(pd.path) + pd.editProductEnvironment(serviceName + ' API', 'dev') + }) + + it('Verify that the dataset created through GWA comand is assocuated with the product', () => { + cy.visit(pd.path) + pd.verifyDataset(serviceName, serviceName + ' API') + }) + + it('Verify service name validation error in new namespace', () => { + const command = [ + 'gwa generate-config --template quick-start', + `--service ${serviceName}`, + '--upstream https://httpbin.org', + '--org ministry-of-health', + '--org-unit planning-and-innovation-division' + ].join(' '); + cy.executeCliCommand('gwa gateway create --generate').then((response) => { + const namespace = response.stdout.match(/\bgw-\w+/g)[0] + cy.executeCliCommand(command).then((response) => { + expect(response.stderr).to.contain(`Error: Service ${serviceName} is already in use. Suggestion: ${namespace}-${serviceName}`) + }); + }); + }) + + after(() => { + cy.logout() +}) + +}) \ No newline at end of file diff --git a/e2e/cypress/tests/16-gwa-cli/02-cli-generate-config.ts b/e2e/cypress/tests/16-gwa-cli/03-cli-generate-config-client-cred.ts similarity index 68% rename from e2e/cypress/tests/16-gwa-cli/02-cli-generate-config.ts rename to e2e/cypress/tests/16-gwa-cli/03-cli-generate-config-client-cred.ts index e090a8ad6..fa0e28413 100644 --- a/e2e/cypress/tests/16-gwa-cli/02-cli-generate-config.ts +++ b/e2e/cypress/tests/16-gwa-cli/03-cli-generate-config-client-cred.ts @@ -5,18 +5,21 @@ import MyAccessPage from '../../pageObjects/myAccess' import HomePage from '../../pageObjects/home'; import Products from '../../pageObjects/products'; const YAML = require('yamljs'); -let userSession: any let cli = require("../../fixtures/test_data/gwa-cli.json") - +const { v4: uuidv4 } = require('uuid') const jose = require('node-jose') +let userSession: any +let namespace: string +const customId = uuidv4().replace(/-/g, '').toLowerCase().substring(0, 3) +const serviceName = 'my-service-' + customId + describe('Verify CLI commands for generate/apply config', () => { const login = new LoginPage() const apiDir = new ApiDirectoryPage() const app = new ApplicationPage() const ma = new MyAccessPage() const pd = new Products() - let namespace: string const home = new HomePage() before(() => { @@ -45,26 +48,38 @@ describe('Verify CLI commands for generate/apply config', () => { }) it('Check gwa command to generate config for client credential template', () => { - cy.executeCliCommand('gwa generate-config --template client-credentials-shared-idp --service my-service --upstream https://httpbin.org --org ministry-of-health --org-unit planning-and-innovation-division --out gw-config.yaml').then((response) => { - expect(response.stdout).to.contain("File gw-config.yaml created") + const serviceName = 'my-service-' + customId + const command = [ + 'gwa generate-config --template client-credentials-shared-idp', + `--service ${serviceName}`, + '--upstream https://httpbin.org', + '--org ministry-of-health', + '--org-unit planning-and-innovation-division', + '--out gw-config-cc.yaml' + ].join(' '); + cy.executeCliCommand(command).then((response) => { + expect(response.stdout).to.contain("File gw-config-cc.yaml created") }); }) it('Check gwa command to apply generated config', () => { - cy.executeCliCommand('gwa apply -i gw-config.yaml').then((response) => { + cy.executeCliCommand('gwa apply -i gw-config-cc.yaml').then((response) => { + expect(response.stdout).to.contain("4/4 Published, 0 Skipped") let wordOccurrences = (response.stdout.match(/\bcreated\b/g) || []).length; expect(wordOccurrences).to.equal(3) - namespace = response.stdout.match(/\bgw-\w+/g)[0] }); }) - it('activates new namespace', () => { - cy.activateGateway(namespace) + it('activates namespace in Portal', () => { + cy.executeCliCommand('gwa gateway current').then((response) => { + namespace = response.stdout.match(/\bgw-\w+/g)[0] + cy.activateGateway(namespace) + }) }) it('Verify that the product created through gwa command is displayed in the portal', () => { cy.visit(pd.path) - pd.editProductEnvironment('my-service API', 'dev') + pd.editProductEnvironment(serviceName + ' API', 'dev') }) it('Verify the Authorization scope and issuer details for the product', () => { @@ -77,11 +92,7 @@ describe('Verify CLI commands for generate/apply config', () => { it('Verify that the dataset created through GWA comand is assocuated with the product', () => { cy.visit(pd.path) - pd.verifyDataset('my-service', 'my-service API') - }) - - it('Navigate to home path', () => { - cy.visit(login.path) + pd.verifyDataset(serviceName, serviceName + ' API') }) after(() => { diff --git a/e2e/cypress/tests/16-gwa-cli/04-cli-gateway-create.ts b/e2e/cypress/tests/16-gwa-cli/04-cli-gateway-create.ts new file mode 100644 index 000000000..f23b355c6 --- /dev/null +++ b/e2e/cypress/tests/16-gwa-cli/04-cli-gateway-create.ts @@ -0,0 +1,131 @@ +import NameSpacePage from '../../pageObjects/namespace' + +const { v4: uuidv4 } = require('uuid') +let cli = require("../../fixtures/test_data/gwa-cli.json") + +let userSession: any +let gatewayId: string +let displayName: string +let customId: string + +var cleanedUrl = Cypress.env('BASE_URL').replace(/^http?:\/\//i, ""); + +describe('Verify "gateway create" and "gateway list" commands', () => { + const ns = new NameSpacePage() + customId = uuidv4().replace(/-/g, '').toLowerCase().substring(0, 3) + + before(() => { + cy.deleteAllCookies() + cy.reload(true) + }) + + beforeEach(() => { + cy.preserveCookies() + cy.fixture('apiowner').as('apiowner') + cy.fixture('common-testdata').as('common-testdata') + }) + + it('authenticates Janis (api owner) to get the user session token', () => { + cy.getUserSessionTokenValue('', false).then((value) => { + userSession = value + }) + }) + + it('Set token with gwa config command', () => { + cy.exec('gwa config set --token ' + userSession, { timeout: 3000, failOnNonZeroExit: false }).then((response) => { + expect(response.stdout).to.contain("Config settings saved") + }); + }) + + it('create gateway - id and display name provided', () => { + cy.get('@common-testdata').then(({ myGateways }: any) => { + gatewayId = myGateways["namespace1"].gatewayId + '-' + customId + displayName = myGateways["namespace1"].displayName + cy.executeCliCommand('gwa gateway create --gateway-id ' + gatewayId + ' --display-name ' + displayName + ' --host ' + cleanedUrl + ' --scheme http').then((response) => { + assert.isNotNaN(response.stdout) + const gatewayIdMatch = response.stdout.match(/Gateway ID: ([\w-]+)/); + if (gatewayIdMatch && gatewayIdMatch[1]) { + const gatewayIdResult = gatewayIdMatch[1]; + assert.equal(gatewayIdResult, gatewayId); + } else { + throw new Error('Failed to extract Gateway ID from response: ' + response.stdout); + } + const displayNameMatch = response.stdout.match(/display name: ([\w-]+)/); + if (displayNameMatch && displayNameMatch[1]) { + const displayNameResult = displayNameMatch[1]; + assert.equal(displayNameResult, displayName); + } else { + throw new Error('Failed to extract Display Name from response: ' + response.stdout); + } + }); + // Verify the created gateway in Portal + cy.visit(ns.listPath) + cy.get(`[data-testid="ns-list-item-${gatewayId}"]`) + .should('contain.text', gatewayId) + .should('contain.text', displayName) + }); + }); + + it('verify gateway list command', () => { + cy.executeCliCommand('gwa gateway list').then((response) => { + assert.isNotNaN(response.stdout) + assert.isTrue(response.stdout.includes(gatewayId), `The output should include the gateway ID: ${gatewayId}`) + assert.isTrue(response.stdout.includes(displayName), `The output should include the display name: ${displayName}`) + }); + cy.deleteGatewayCli(gatewayId, false) + }); + + it('create gateway - id provided', () => { + cy.get('@common-testdata').then(({ myGateways }: any) => { + gatewayId = myGateways["namespace2"].gatewayId + '-' + customId + displayName = "janis's Gateway" + cy.executeCliCommand('gwa gateway create --gateway-id ' + gatewayId + ' --host ' + cleanedUrl + ' --scheme http').then((response) => { + assert.isNotNaN(response.stdout) + const gatewayIdMatch = response.stdout.match(/Gateway ID: ([\w-]+)/); + if (gatewayIdMatch && gatewayIdMatch[1]) { + const gatewayIdResult = gatewayIdMatch[1]; + assert.equal(gatewayIdResult, gatewayId); + } else { + throw new Error('Failed to extract Gateway ID from response: ' + response.stdout); + } + }); + // Verify the created gateway + cy.visit(ns.listPath) + cy.get(`[data-testid="ns-list-item-${gatewayId}"]`) + .should('contain.text', gatewayId) + .should('contain.text', displayName) + cy.deleteGatewayCli(gatewayId, false) + }); + }); + + it('create gateway - display name provided', () => { + cy.get('@common-testdata').then(({ myGateways }: any) => { + let generatedGatewayId: string + displayName = myGateways["namespace3"].displayName + cy.executeCliCommand('gwa gateway create --display-name ' + displayName + ' --host ' + cleanedUrl + ' --scheme http').then((response) => { + assert.isNotNaN(response.stdout) + const gatewayIdMatch = response.stdout.match(/Gateway ID: ([\w-]+)/); + generatedGatewayId = gatewayIdMatch[1]; + const displayNameMatch = response.stdout.match(/display name: ([\w-]+)/); + if (displayNameMatch && displayNameMatch[1]) { + const displayNameResult = displayNameMatch[1]; + assert.equal(displayNameResult, displayName); + } else { + throw new Error('Failed to extract Display Name from response: ' + response.stdout); + } + // Verify the created gateway + cy.visit(ns.listPath) + cy.get(`[data-testid="ns-list-item-${generatedGatewayId}"]`) + .should('contain.text', generatedGatewayId) + .should('contain.text', displayName) + cy.deleteGatewayCli(generatedGatewayId, false) + }); + }); + }); + + after(() => { + cy.logout() + cy.clearLocalStorage({ log: true }) + cy.deleteAllCookies() + }) +}) diff --git a/e2e/cypress/tests/20-gateways/01-list.ts b/e2e/cypress/tests/20-gateways/01-list.ts index e95963d85..c31ad87f1 100644 --- a/e2e/cypress/tests/20-gateways/01-list.ts +++ b/e2e/cypress/tests/20-gateways/01-list.ts @@ -1,4 +1,3 @@ -import { report } from 'process' import NameSpacePage from '../../pageObjects/namespace' let gateways: any @@ -34,7 +33,6 @@ describe('My Gateways list page', () => { it('create a set of namespaces', () => { cy.get('@common-testdata').then(({ myGateways }: any) => { gateways = myGateways - gateways["namespace1"] = gateways["namespace1"] Cypress._.forEach(gateways, (gateway) => { cy.createGateway(gateway.gatewayId + '-' + customId, gateway.displayName); }); diff --git a/e2e/test-data-dependencies.md b/e2e/test-data-dependencies.md index 06314847e..519cb98e1 100644 --- a/e2e/test-data-dependencies.md +++ b/e2e/test-data-dependencies.md @@ -102,7 +102,9 @@ | │   08-namespaces.cy.ts | 1.1 and 15.1 | | 16-gwa-cli | | | │   01-cli-commands.ts | NA | -| │   02-cli-generate-config.ts | 16.1 | +| │   02-cli-generate-config-quick-start.ts | 16.1 | +| │   03-cli-generate-config-client-cred.ts | 1.1, 2.1 to 2.3, 15.1, 15.5. 16.1 | +| │   04-cli-gateway-create.ts | NA | | 17-delete-application | | | │   01-delete-application-without-access.cy.ts | NA | | │   02-delete-application-with-pending-request.cy.ts | 17.1 (CONFIRM: requires product, created in 01-api-key?) | diff --git a/src/nextapp/components/namespace-access/organization-groups-access.tsx b/src/nextapp/components/namespace-access/organization-groups-access.tsx index 61eef69b0..84c537e15 100644 --- a/src/nextapp/components/namespace-access/organization-groups-access.tsx +++ b/src/nextapp/components/namespace-access/organization-groups-access.tsx @@ -136,7 +136,7 @@ const OrganizationGroupsAccess: React.FC = ({ {d.scopes.map((t) => ( - {t} + {t.replace(/Namespace/g, 'Gateway')} ))} diff --git a/src/nextapp/components/no-gateway-redirect/no-gateway-redirect.tsx b/src/nextapp/components/no-gateway-redirect/no-gateway-redirect.tsx index 414fdb3db..a9f45fcd6 100644 --- a/src/nextapp/components/no-gateway-redirect/no-gateway-redirect.tsx +++ b/src/nextapp/components/no-gateway-redirect/no-gateway-redirect.tsx @@ -12,6 +12,7 @@ const NoGatewayRedirect = () => { router.push('/manager/gateways/list'); } }, [hasNamespace]); + return null }; export default NoGatewayRedirect; diff --git a/src/nextapp/pages/manager/gateways/detail.tsx b/src/nextapp/pages/manager/gateways/detail.tsx index 1bf000e92..4a93ca708 100644 --- a/src/nextapp/pages/manager/gateways/detail.tsx +++ b/src/nextapp/pages/manager/gateways/detail.tsx @@ -158,6 +158,9 @@ const NamespacesPage: React.FC = () => { text: 'Your Organization and Business Unit will appear here', }; }, [namespace]); + useEffect(() => { + window.scrollTo(0, 0); + }, []); const handleDelete = React.useCallback(async () => { if (user?.namespace) { toast({