diff --git a/.github/workflows/aps-cypress-e2e.yaml b/.github/workflows/aps-cypress-e2e.yaml index e918f17a6..8a8046f89 100644 --- a/.github/workflows/aps-cypress-e2e.yaml +++ b/.github/workflows/aps-cypress-e2e.yaml @@ -106,7 +106,15 @@ jobs: run: | FAILURE_COUNT=$(cat ${{ github.workspace }}/e2e/results/bcgov-aps-e2e-report.json | jq '.stats.failures') if [[ "$FAILURE_COUNT" -gt 0 ]]; then - FAILED_TESTS=$(jq -r '.results[] | {file: .file, suites: .suites[]} | .suites as $suite | {file: .file, failed_tests: ($suite.tests[] | select(.fail == true) | {title})} | select(.failed_tests != null) | "- " + (.file | split("/") | .[2:] | join("/")) + " - " + .failed_tests.title' ${{ github.workspace }}/e2e/results/bcgov-aps-e2e-report.json) + FAILED_TESTS=$(jq -r ' + .results[] | + (.file | split("/") | .[2:] | join("/")) as $file | + .. | + .tests? // empty | + .[] | + select(.fail == true) | + "- " + $file + " - " + .title + ' ${{ github.workspace }}/e2e/results/bcgov-aps-e2e-report.json) STATS=$(cat ${{ github.workspace }}/e2e/results/bcgov-aps-e2e-report.json | jq -r '.stats | to_entries | map("\(.key)\t\(.value)") | .[]' | column -t) echo -e "Stats: $STATS\n\nFailed Tests:\n$FAILED_TESTS\n\nRun Link: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" > msg export MSG=$(cat msg) diff --git a/e2e/cypress.config.ts b/e2e/cypress.config.ts index 56bfe6029..43b2102f1 100644 --- a/e2e/cypress.config.ts +++ b/e2e/cypress.config.ts @@ -39,6 +39,7 @@ export default defineConfig({ './cypress/tests/17-*/*.ts', './cypress/tests/18-*/*.ts', './cypress/tests/19-*/*.ts', + './cypress/tests/20-*/*.ts', ] return config }, diff --git a/e2e/cypress/fixtures/api.json b/e2e/cypress/fixtures/api.json index 83779b63f..9ef3f2343 100644 --- a/e2e/cypress/fixtures/api.json +++ b/e2e/cypress/fixtures/api.json @@ -185,7 +185,7 @@ "owner": "janis@idir" }, "shared_IDP_update_body": { - "name": "Sample Shared IdP", + "name": "Sample Shared IdP updated", "description": "A Shared IdP for Teams to use", "flow": "client-credentials", "clientAuthenticator": "client-secret", diff --git a/e2e/cypress/fixtures/common-testdata.json b/e2e/cypress/fixtures/common-testdata.json index f3c7cf668..98e24a71f 100644 --- a/e2e/cypress/fixtures/common-testdata.json +++ b/e2e/cypress/fixtures/common-testdata.json @@ -10,7 +10,7 @@ "namespace": "gw-16a3a" }, "orgAssignment": { - "namespace": "gw-f016e" + "namespace": "gw-f6a4d" }, "orgAssignmentMultipleAdmin": { "namespace": "orgassignment1" @@ -81,5 +81,26 @@ }, "twoTieredHidden": { "namespace": "two-tier-hidden" + }, + "serviceAvailability": { + "namespace": "service-avail" + }, + "myGateways": { + "namespace1": { + "gatewayId": "gw-apple", + "displayName": "Apple" + }, + "namespace2": { + "gatewayId": "gw-banana", + "displayName": "Banana" + }, + "namespace3": { + "gatewayId": "gw-date", + "displayName": "Cherry" + }, + "namespace4": { + "gatewayId": "gw-cherry", + "displayName": "Cherry" + } } } \ No newline at end of file diff --git a/e2e/cypress/fixtures/service-availability.yml b/e2e/cypress/fixtures/service-availability.yml new file mode 100644 index 000000000..64851f962 --- /dev/null +++ b/e2e/cypress/fixtures/service-availability.yml @@ -0,0 +1,19 @@ +kind: GatewayService +name: taken-service-name +host: httpbin.org +tags: [ns.service-avail] +port: 446 +protocol: https +retries: 0 +routes: + - name: taken-service-name + tags: [ns.service-avail] + hosts: + - taken-service-name.api.gov.bc.ca + paths: + - / + methods: + - GET + strip_path: false + https_redirect_status_code: 426 + path_handling: v0 diff --git a/e2e/cypress/pageObjects/apiDirectory.ts b/e2e/cypress/pageObjects/apiDirectory.ts index d0aaf834f..7d69a1584 100644 --- a/e2e/cypress/pageObjects/apiDirectory.ts +++ b/e2e/cypress/pageObjects/apiDirectory.ts @@ -1,6 +1,7 @@ class ApiDirectoryPage { path: string = '/devportal/api-directory' + yourProductsPath: string = '/devportal/api-directory/your-products' rqstAccessBtn: string = '[data-testid=request-access-button]' appSelect: string = '[data-testid=access-application-select]' additionalNotes: string = '[data-testid=access-rqst-add-notes-text]' diff --git a/e2e/cypress/pageObjects/namespace.ts b/e2e/cypress/pageObjects/namespace.ts index b38b5227b..7835e4612 100644 --- a/e2e/cypress/pageObjects/namespace.ts +++ b/e2e/cypress/pageObjects/namespace.ts @@ -1,6 +1,9 @@ class NameSpacePage { path: string = '/manager/gateways' + listPath: string = 'manager/gateways/list' detailPath: string = '/manager/gateways/detail' + getStartedPath: string = '/manager/gateways/get-started' + listFilterSelect: string = '[data-testid="ns-filter-select"]' gatewayServiceLink: string = '[data-testid="ns-manage-link-Gateway Services"]' productsLink: string = '[data-testid="ns-manage-link-Gateway Services"]' consumersLink: string = '[data-testid="ns-manage-link-Consumers"]' diff --git a/e2e/cypress/support/auth-commands.ts b/e2e/cypress/support/auth-commands.ts index 2c52f515f..4d040f156 100644 --- a/e2e/cypress/support/auth-commands.ts +++ b/e2e/cypress/support/auth-commands.ts @@ -101,6 +101,16 @@ Cypress.Commands.add('createGateway', (gatewayid?: string, displayname?: string) ) }) +Cypress.Commands.add('deleteGatewayCli', (gatewayid: string, force: boolean = false) => { + cy.executeCliCommand('gwa config set gateway ' + gatewayid).then(() => { + cy.executeCliCommand(`gwa gateway destroy ${force ? '--force' : ''}`).then( + (response) => { + console.log(response) + expect(response.stdout).to.contain('Gateway destroyed: ' + gatewayid) + }) + }) +}) + Cypress.Commands.add('activateGateway', (gatewayId: string, checkNoNamespace: boolean = false) => { const getAllNsQuery = ` query GetNamespaces { @@ -209,7 +219,7 @@ Cypress.Commands.add('resetCredential', (accessRole: string) => { Cypress.Commands.add( 'getUserSessionTokenValue', - (namespace: string, isNamespaceSelected?: boolean) => { + (namespace: string = 'ns', isNamespaceSelected?: boolean) => { const login = new LoginPage() const home = new HomePage() const na = new NamespaceAccessPage() @@ -219,16 +229,16 @@ Cypress.Commands.add( cy.fixture('apiowner').as('apiowner') cy.preserveCookies() cy.visit(login.path) - cy.interceptUserSession().then(() => { - cy.get('@apiowner').then(({ user }: any) => { - cy.login(user.credentials.username, user.credentials.password) - cy.log('Logged in!') - // cy.activateGateway(apiTest.namespace) - if (isNamespaceSelected || undefined) { - cy.activateGateway(namespace) - } + cy.get('@apiowner').then(({ user }: any) => { + cy.login(user.credentials.username, user.credentials.password) + cy.log('Logged in!') + // cy.activateGateway(apiTest.namespace) + if (isNamespaceSelected || undefined) { + cy.activateGateway(namespace) + } + cy.getUserSession().then(() => { cy.get('@login').then(function (xhr: any) { - userSession = xhr.response.headers['x-auth-request-access-token'] + userSession = xhr.headers['x-auth-request-access-token'] return userSession }) }) diff --git a/e2e/cypress/support/global.d.ts b/e2e/cypress/support/global.d.ts index a96743847..433e29b3f 100644 --- a/e2e/cypress/support/global.d.ts +++ b/e2e/cypress/support/global.d.ts @@ -38,6 +38,8 @@ declare namespace Cypress { displayname?: string, ): Chainable + deleteGatewayCli(gatewayid: string, force: boolean): Chainable + activateGateway( gatewayId: string, checkNoNamespace?: boolean diff --git a/e2e/cypress/tests/01-api-key/00-get-started.cy.ts b/e2e/cypress/tests/01-api-key/00-get-started.cy.ts new file mode 100644 index 000000000..60d30074e --- /dev/null +++ b/e2e/cypress/tests/01-api-key/00-get-started.cy.ts @@ -0,0 +1,74 @@ +import LoginPage from '../../pageObjects/login' +import NameSpacePage from '../../pageObjects/namespace' +const cleanedUrl = Cypress.env('BASE_URL').replace(/^http?:\/\//i, ""); + +// Elements of this page must be checked when there are no Gateways, so this test comes first of all +describe('Gateway Get Started page', () => { + const login = new LoginPage() + const ns = new NameSpacePage() + let userSession: any + let namespace: string + + before(() => { + cy.visit('/') + cy.deleteAllCookies() + cy.reload(true) + cy.resetState() + }) + + 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('Set environment with gwa config command', () => { + cy.executeCliCommand('gwa config set --host ' + cleanedUrl + ' --scheme http').then((response) => { + expect(response.stdout).to.contain("Config settings saved") + }); + }) + + it('Check for redirect to Get Started page', () => { + cy.visit(ns.path) + cy.url().should('include', '/manager/gateways/get-started') + }) + + it('Check for box that says "No gateways"', () => { + cy.get('[data-testid="no-gateways"]').should('exist') + }) + + it('Create a namespace', () => { + cy.createGateway().then((response) => { + namespace = response.gatewayId + }) + }) + + it('Check for banner that says "You have gateways"', () => { + cy.reload() + cy.get('[data-testid="no-gateways"]').should('not.exist') + cy.get('[data-testid="you-have-gateways-banner"]').should('exist') + }) + + it('Cleanup: delete namespace', () => { + cy.deleteGatewayCli(namespace, true) + }) + + after(() => { + cy.logout() + cy.clearLocalStorage({ log: true }) + cy.deleteAllCookies() + }) + +}) \ No newline at end of file diff --git a/e2e/cypress/tests/01-api-key/01-create-api.cy.ts b/e2e/cypress/tests/01-api-key/01-create-api.cy.ts index 1c29bfbad..c6c8dfde8 100644 --- a/e2e/cypress/tests/01-api-key/01-create-api.cy.ts +++ b/e2e/cypress/tests/01-api-key/01-create-api.cy.ts @@ -65,13 +65,6 @@ describe('Create API Spec', () => { }) }) - // TODO: Update this test to use gwa cli (if not covered in other tests) - // it('Verify for invalid namespace name', () => { - // cy.get('@apiowner').then(({ invalid_namespace }: any) => { - // home.validateNamespaceName(invalid_namespace) - // }) - // }) - it('creates a new service account', () => { cy.visit(sa.path) cy.get('@apiowner').then(({ serviceAccount }: any) => { diff --git a/e2e/cypress/tests/09-update-product-env/06-shared-idp.cy.ts b/e2e/cypress/tests/09-update-product-env/06-shared-idp.cy.ts index e6e6adefb..42ce5c3dc 100644 --- a/e2e/cypress/tests/09-update-product-env/06-shared-idp.cy.ts +++ b/e2e/cypress/tests/09-update-product-env/06-shared-idp.cy.ts @@ -117,14 +117,6 @@ describe('Update IDP issuer for shared IDP profile', () => { }) }) - it('Prepare the Request Specification for the API', () => { - cy.get('@api').then(({ authorizationProfiles }: any) => { - cy.setHeaders(authorizationProfiles.headers) - cy.setAuthorizationToken(userSession) - cy.setRequestBody(authorizationProfiles.shared_IDP_body) - }) - }) - it('Prepare the Request Specification for the API', () => { cy.get('@api').then(({ authorizationProfiles }: any) => { cy.setHeaders(authorizationProfiles.headers) diff --git a/e2e/cypress/tests/10-clear-resources/05-delete-resources.cy.ts b/e2e/cypress/tests/10-clear-resources/05-delete-resources.cy.ts index 23fb01161..855994ad4 100644 --- a/e2e/cypress/tests/10-clear-resources/05-delete-resources.cy.ts +++ b/e2e/cypress/tests/10-clear-resources/05-delete-resources.cy.ts @@ -4,6 +4,8 @@ import NameSpacePage from '../../pageObjects/namespace' import Products from '../../pageObjects/products' import ServiceAccountsPage from '../../pageObjects/serviceAccounts' +const cleanedUrl = Cypress.env('BASE_URL').replace(/^http?:\/\//i, ""); + describe('Delete created resources', () => { const login = new LoginPage() const home = new HomePage() @@ -11,6 +13,7 @@ describe('Delete created resources', () => { const pd = new Products() const ns = new NameSpacePage let flag: boolean + let userSession: any before(() => { cy.visit('/') @@ -30,6 +33,9 @@ describe('Delete created resources', () => { cy.get('@common-testdata').then(({ deleteResources }: any) => { cy.login(user.credentials.username, user.credentials.password) cy.activateGateway(deleteResources.namespace); + cy.getUserSessionTokenValue(deleteResources.namespace, false).then((value) => { + userSession = value + }) }) }) }) @@ -57,11 +63,21 @@ describe('Delete created resources', () => { sa.deleteAllServiceAccounts() }) - it('Delete Namespace', () => { + 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('Set environment with gwa config command', () => { + cy.executeCliCommand('gwa config set --host ' + cleanedUrl + ' --scheme http').then((response) => { + expect(response.stdout).to.contain("Config settings saved") + }); + }) + + it('Delete Namespace (CLI)', () => { cy.get('@common-testdata').then(({ deleteResources }: any) => { - cy.visit(ns.detailPath) - ns.deleteNamespace(deleteResources.namespace) - cy.wait(10000) + cy.deleteGatewayCli(deleteResources.namespace, true) }); }); @@ -71,14 +87,30 @@ describe('Delete created resources', () => { cy.wrap(null).then(() => { return cy.getGateways(); }).then((result) => { - console.log(result); const namespaceNames = result.map((ns: { name: any }) => ns.name); - console.log(namespaceNames); expect(namespaceNames).to.not.include(deleteResources.namespace); }); }); }); + it('Verify delete namespace works from the UI', () => { + cy.createGateway().then((response) => { + const namespace = response.gatewayId + cy.log('New namespace created: ' + namespace) + cy.activateGateway(namespace); + cy.visit(ns.detailPath) + ns.deleteNamespace(namespace) + + cy.visit('/') + cy.wrap(null).then(() => { + return cy.getGateways(); + }).then((result) => { + const namespaceNames = result.map((ns: { name: any }) => ns.name); + expect(namespaceNames).to.not.include(namespace); + }); + }); + }); + after(() => { cy.logout() diff --git a/e2e/cypress/tests/14-org-assignment/01-client-cred-team-access.ts b/e2e/cypress/tests/14-org-assignment/01-client-cred-team-access.ts index 4e8e5fcf7..9c6c998f9 100644 --- a/e2e/cypress/tests/14-org-assignment/01-client-cred-team-access.ts +++ b/e2e/cypress/tests/14-org-assignment/01-client-cred-team-access.ts @@ -15,6 +15,7 @@ describe('Add Organization to publish API', () => { const login = new LoginPage() const home = new HomePage() const na = new NamespaceAccessPage() + const ns = new NameSpacePage() const pd = new Products() const sa = new ServiceAccountsPage() const apiDir = new ApiDirectoryPage() @@ -114,6 +115,15 @@ describe('Add Organization to publish API', () => { }) }) + it('Verify My Gateways shows publishing "disabled"', () => { + cy.visit(ns.listPath) + cy.get(`[data-testid="ns-list-item-${namespace}"]`).should('contain.text', 'Publishing disabled') + cy.get(ns.listFilterSelect).select('disabled') + cy.get(`[data-testid="ns-list-item-${namespace}"]`).should('exist') + cy.get(ns.listFilterSelect).select('enabled') + cy.get(`[data-testid="ns-list-item-${namespace}"]`).should('not.exist') + }) + it('Assign organization to the created namespace', () => { cy.visit(apiDir.path) cy.get('@apiowner').then(({ product }: any) => { @@ -121,6 +131,15 @@ describe('Add Organization to publish API', () => { }) }) + it('Verify My Gateways shows publishing "pending"', () => { + cy.visit(ns.listPath) + cy.get(`[data-testid="ns-list-item-${namespace}"]`).should('contain.text', 'Pending publishing permission') + cy.get(ns.listFilterSelect).select('pending') + cy.get(`[data-testid="ns-list-item-${namespace}"]`).should('exist') + cy.get(ns.listFilterSelect).select('enabled') + cy.get(`[data-testid="ns-list-item-${namespace}"]`).should('not.exist') + }) + it('Verify Organization Administrator notification banner', () => { cy.visit(apiDir.path) cy.get('@apiowner').then(({ orgAssignment }: any) => { @@ -182,6 +201,7 @@ describe('Org Admin approves the request', () => { describe('Activate the API to make it visible in API Directory', () => { const login = new LoginPage() const home = new HomePage() + const ns = new NameSpacePage() const pd = new Products() const apiDir = new ApiDirectoryPage() @@ -203,6 +223,15 @@ describe('Activate the API to make it visible in API Directory', () => { }) }) + it('Verify My Gateways shows publishing "enabled"', () => { + cy.visit(ns.listPath) + cy.get(`[data-testid="ns-list-item-${namespace}"]`).should('contain.text', 'Publishing enabled') + cy.get(ns.listFilterSelect).select('enabled') + cy.get(`[data-testid="ns-list-item-${namespace}"]`).should('exist') + cy.get(ns.listFilterSelect).select('disabled') + cy.get(`[data-testid="ns-list-item-${namespace}"]`).should('not.exist') + }) + it('update the Dataset in BC Data Catelogue to appear the API in the Directory', () => { cy.visit(pd.path) cy.get('@apiowner').then(({ orgAssignment }: any) => { diff --git a/e2e/cypress/tests/16-gwa-cli/02-cli-generate-config.ts b/e2e/cypress/tests/16-gwa-cli/02-cli-generate-config.ts index 8f0e0a1aa..e090a8ad6 100644 --- a/e2e/cypress/tests/16-gwa-cli/02-cli-generate-config.ts +++ b/e2e/cypress/tests/16-gwa-cli/02-cli-generate-config.ts @@ -45,7 +45,7 @@ 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').then((response) => { + 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") }); }) diff --git a/e2e/cypress/tests/19-api-v3/02-organization.ts b/e2e/cypress/tests/19-api-v3/02-organization.ts index 67438850b..4a233992f 100644 --- a/e2e/cypress/tests/19-api-v3/02-organization.ts +++ b/e2e/cypress/tests/19-api-v3/02-organization.ts @@ -136,13 +136,20 @@ describe('Organization', () => { }) it('GET /organizations/{org}/activity', () => { - cy.callAPI('ds/api/v3/organizations/ministry-of-health/activity', 'GET').then( - ({ apiRes: { status, body } }: any) => { - expect(status).to.be.equal(200) - // expect(JSON.stringify(body.filter(a => a.params.ns == ))).to.be.equal(JSON.stringify(match)) - } - ) - }) + // Retry logic if the 422 error occurs + cy.callAPI('ds/api/v3/organizations/ministry-of-health/activity', 'GET') + .then(({ apiRes: { status, body } }: any) => { + if (status === 422) { + cy.wait(2000); + cy.callAPI('ds/api/v3/organizations/ministry-of-health/activity', 'GET') + .then(({ apiRes: { status, body } }: any) => { + expect(status).to.be.equal(200); + }); + } else { + expect(status).to.be.equal(200); + } + }); + }); it('PUT /organizations/{org}/{orgUnit}/gateways/{gatewayId}', () => { cy.setRequestBody({}) diff --git a/e2e/cypress/tests/19-api-v3/03-gateways.ts b/e2e/cypress/tests/19-api-v3/03-gateways.ts index 167c8d0d1..09db841b3 100644 --- a/e2e/cypress/tests/19-api-v3/03-gateways.ts +++ b/e2e/cypress/tests/19-api-v3/03-gateways.ts @@ -210,7 +210,7 @@ describe('Gateways', () => { details: { d0: { message: - 'Display name can not be longer than 30 characters and can only use special characters "-()_ .\'/".', + 'Display name must be between 3 and 30 characters, starting with an alpha-numeric character, and can only use special characters "-()_ .\'/".', }, }, } @@ -232,7 +232,7 @@ describe('Gateways', () => { details: { d0: { message: - 'Display name can not be longer than 30 characters and can only use special characters "-()_ .\'/".', + 'Display name must be between 3 and 30 characters, starting with an alpha-numeric character, and can only use special characters "-()_ .\'/".', }, }, } diff --git a/e2e/cypress/tests/19-api-v3/05-issuers.ts b/e2e/cypress/tests/19-api-v3/05-issuers.ts index 2e903b6fe..9e50c781e 100644 --- a/e2e/cypress/tests/19-api-v3/05-issuers.ts +++ b/e2e/cypress/tests/19-api-v3/05-issuers.ts @@ -37,11 +37,11 @@ describe('Authorization Profiles', () => { const issuer = body[0] expect(issuer.name).to.be.equal(`my-auth-profile-for-${gateway.gatewayId}`) - expect(issuer.environmentDetails[0].environment).to.be.equal('test') - expect(issuer.environmentDetails[0].issuerUrl).to.be.equal( + expect(issuer.environmentDetails[1].environment).to.be.equal('test') + expect(issuer.environmentDetails[1].issuerUrl).to.be.equal( Cypress.env('OIDC_ISSUER') ) - expect(issuer.environmentDetails[0].clientId).to.be.equal( + expect(issuer.environmentDetails[1].clientId).to.be.equal( `ap-my-auth-profile-for-${gateway.gatewayId}-test` ) } diff --git a/e2e/cypress/tests/19-api-v3/07-endpoints.ts b/e2e/cypress/tests/19-api-v3/07-endpoints.ts index 469887276..c2e8d47e8 100644 --- a/e2e/cypress/tests/19-api-v3/07-endpoints.ts +++ b/e2e/cypress/tests/19-api-v3/07-endpoints.ts @@ -1,4 +1,4 @@ -describe('Endpoints', () => { +describe('Endpoints - unused service', () => { it('GET /routes/availability', () => { cy.callAPI( 'ds/api/v3/routes/availability?gatewayId=gw-1234&serviceName=testme', @@ -23,3 +23,79 @@ describe('Endpoints', () => { }) }) }) + +describe('Endpoints - used service', () => { + let userSession: any + + beforeEach(() => { + cy.fixture('common-testdata').as('common-testdata') + }) + + it('authenticates Janis (api owner) to get the user session token', () => { + cy.get('@common-testdata').then(({ serviceAvailability }: any) => { + cy.getUserSessionTokenValue(serviceAvailability.namespace, false).then((value) => { + console.log(value) + userSession = value + }) + }) + }) + + it('Check gwa config command to set environment', () => { + var cleanedUrl = Cypress.env('BASE_URL').replace(/^http?:\/\//i, '') + cy.executeCliCommand('gwa config set --host ' + cleanedUrl + ' --scheme http').then( + (response) => { + expect(response.stdout).to.contain('Config settings saved') + } + ) + }) + + 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('create namespace with cli', () => { + cy.get('@common-testdata').then(({ serviceAvailability }: any) => { + cy.executeCliCommand( + 'gwa gateway create --gateway-id ' + serviceAvailability.namespace + ' --display-name="Service Availability"' + ).then((response) => { + expect(response.stdout).to.contain(serviceAvailability.namespace) + }) + }) + }) + + it('Upload config for key-auth', () => { + cy.executeCliCommand('gwa apply -i ./cypress/fixtures/service-availability.yml').then((response) => { + expect(response.stdout).to.contain('Gateway Services published'); + }) + }) + + it('GET /routes/availability', () => { + cy.callAPI( + 'ds/api/v3/routes/availability?gatewayId=gw-1234&serviceName=taken-service-name', + 'GET' + ).then(({ apiRes: { status, body } }: any) => { + const match = { + available: false, + suggestion: { + serviceName: "gw-1234-taken-service-name", + names: [ + "gw-1234-taken-service-name", + "gw-1234-taken-service-name-dev", + "gw-1234-taken-service-name-test" + ], + hosts: [ + "gw-1234-taken-service-name.api.gov.bc.ca", + "gw-1234-taken-service-name.dev.api.gov.bc.ca", + "gw-1234-taken-service-name.test.api.gov.bc.ca", + "gw-1234-taken-service-name-api-gov-bc-ca.dev.api.gov.bc.ca", + "gw-1234-taken-service-name-api-gov-bc-ca.test.api.gov.bc.ca" + ] + }, + } + expect(status).to.be.equal(200) + expect(JSON.stringify(body)).to.be.equal(JSON.stringify(match)) + }) + }) +}) \ No newline at end of file diff --git a/e2e/cypress/tests/20-gateways/01-list.ts b/e2e/cypress/tests/20-gateways/01-list.ts new file mode 100644 index 000000000..e95963d85 --- /dev/null +++ b/e2e/cypress/tests/20-gateways/01-list.ts @@ -0,0 +1,140 @@ +import { report } from 'process' +import NameSpacePage from '../../pageObjects/namespace' +let gateways: any + +const { v4: uuidv4 } = require('uuid') +const customId = uuidv4().replace(/-/g, '').toLowerCase().substring(0, 3) + +describe('My Gateways list page', () => { + const ns = new NameSpacePage() + let userSession: any + + before(() => { + cy.deleteAllCookies() + }) + + 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 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); + }); + }); + }); + + it('Verify My Gateways shows the created gateways', () => { + cy.visit(ns.listPath) + Cypress._.forEach(gateways, (gateway) => { + cy.get(`[data-testid="ns-list-item-${gateway.gatewayId + '-' + customId}"]`) + .should('contain.text', gateway.displayName) + }); + }) + + it('Check Gateway link goes to details page', () => { + cy.visit(ns.listPath) + cy.get(`[data-testid="ns-list-activate-link-${gateways["namespace1"].gatewayId + '-' + customId}"]`).click() + cy.url().should('include', '/manager/gateways/detail') + cy.get('h1').should('contain.text', gateways["namespace1"].displayName) + }) + + it('Test search - find results', () => { + cy.visit(ns.listPath) + cy.get('[data-testid="ns-search-input"]').type(gateways["namespace1"].displayName) + cy.get(`[data-testid="ns-list-item-${gateways["namespace1"].gatewayId + '-' + customId}"]`).should('exist') + cy.get(`[data-testid="ns-list-item-${gateways["namespace2"].gatewayId + '-' + customId}"]`).should('not.exist') + cy.get('[data-testid="ns-search-input"]').clear() + cy.get(`[data-testid="ns-list-item-${gateways["namespace2"].gatewayId + '-' + customId}"]`).should('exist') + }) + + it('Test search - do not find results', () => { + cy.visit(ns.listPath) + cy.get('[data-testid="ns-search-input"]').type('gibberish') + cy.get(`[data-testid="ns-list-item-${gateways["namespace1"].gatewayId + '-' + customId}"]`).should('not.exist') + cy.get('[data-testid="ns-no-results-text"]').should('exist') + }) + + it('Test filter - find results', () => { + cy.visit(ns.listPath) + cy.get('[data-testid="ns-filter-select"]').select('disabled') + cy.get(`[data-testid="ns-list-item-${gateways["namespace1"].gatewayId + '-' + customId}"]`).should('exist') + }) + + it('Test filter - do not find results', () => { + cy.visit(ns.listPath) + cy.get('[data-testid="ns-filter-select"]').select('pending') + cy.get(`[data-testid="ns-list-item-${gateways["namespace1"].gatewayId + '-' + customId}"]`).should('not.exist') + }) + + it('Test filter and search together', () => { + cy.visit(ns.listPath) + cy.get('[data-testid="ns-filter-select"]').select('disabled') + cy.get('[data-testid="ns-search-input"]').type(gateways["namespace1"].displayName) + cy.get(`[data-testid="ns-list-item-${gateways["namespace1"].gatewayId + '-' + customId}"]`).should('exist') + }) + + it('Edit Gateway display name - valid', () => { + cy.visit(ns.listPath) + cy.get(`[data-testid="ns-list-activate-link-${gateways["namespace1"].gatewayId + '-' + customId}"]`).click() + cy.get('[data-testid="display-name-edit-btn"]').click() + cy.get('[data-testid="edit-display-name-input"]').type(' Pie') + cy.get('[data-testid="edit-display-name-submit-btn"]').click() + cy.get('h1').should('contain.text', 'Apple Pie') + }) + + it('Edit Gateway display name - too short or too long', () => { + cy.visit(ns.listPath) + cy.get(`[data-testid="ns-list-activate-link-${gateways["namespace1"].gatewayId + '-' + customId}"]`).click() + cy.get('[data-testid="display-name-edit-btn"]').click() + cy.get('[data-testid="edit-display-name-input"]').clear().type('12') + cy.get('[data-testid="edit-display-name-submit-btn"]').should('be.disabled') + cy.get('[data-testid="edit-display-name-input"]').clear().type('Supercalifragilisticexpialidocious') + cy.get('[data-testid="edit-display-name-submit-btn"]').should('be.disabled') + cy.get('[data-testid="edit-display-name-input"]').clear().type('A reasonable name') + cy.get('[data-testid="edit-display-name-submit-btn"]').should('be.enabled') + cy.get('[data-testid="edit-display-name-cancel-btn"]').click() + }) + + it('Export Gateway Report', () => { + cy.visit(ns.listPath) + cy.get('[data-testid="ns-report-btn"]').click() + cy.get('[data-testid="export-report-select-all-check"]').click() + cy.get('[data-testid="export-report-export-btn"]').click() + // check that the report is downloaded + const filePath = 'cypress/downloads/gateway-report.xlsx' + cy.readFile(filePath, 'binary', { timeout: 10000 }).then((fileContent) => { + expect(fileContent.length).to.be.greaterThan(0); + }); + }); + + it('Cleanup: delete namespaces', () => { + Cypress._.forEach(gateways, (gateway) => { + cy.deleteGatewayCli(gateway.gatewayId + '-' + customId, false) + }); + }) + + after(() => { + cy.logout() + cy.clearLocalStorage({ log: true }) + cy.deleteAllCookies() + }) + +}) \ No newline at end of file diff --git a/e2e/cypress/tests/20-gateways/02-dropdown.ts b/e2e/cypress/tests/20-gateways/02-dropdown.ts new file mode 100644 index 000000000..8841c581c --- /dev/null +++ b/e2e/cypress/tests/20-gateways/02-dropdown.ts @@ -0,0 +1,130 @@ +import NameSpacePage from '../../pageObjects/namespace' +import ApiDirectoryPage from '../../pageObjects/apiDirectory' +let gateways: any +let gateway1: any +let totalGateways: number + +const { v4: uuidv4 } = require('uuid') +const customId = uuidv4().replace(/-/g, '').toLowerCase().substring(0, 3) + +describe('Gateway selector dropdown', () => { + const ns = new NameSpacePage() + const ad = new ApiDirectoryPage() + let userSession: any + + before(() => { + cy.deleteAllCookies() + cy.clearLocalStorage({ log: 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('Get current total number of gateways', () => { + // Create a new gateway to ensure there is at least one gateway + cy.createGateway(); + + cy.visit(ad.yourProductsPath); + cy.get('[data-testid="ns-dropdown-btn"]').click(); + cy.get('[data-testid="ns-dropdown-total-gateways"]').should('be.visible').then(($el) => { + const totalText = $el.text(); + totalGateways = parseInt( + totalText.replace(/You have /, '').replace(/ Gateways in total/, '') + ); + cy.log('Existing gateways: ' + totalGateways); + }); + }) + + it('create a set of namespaces', () => { + cy.get('@common-testdata').then(({ myGateways }: any) => { + gateways = myGateways + gateway1 = gateways["namespace1"] + Cypress._.forEach(gateways, (gateway) => { + cy.createGateway(gateway.gatewayId + '-' + customId, gateway.displayName); + }); + }); + }); + + it('Verify dropdown shows the new total number of gateways', () => { + cy.visit(ad.yourProductsPath) + cy.get('[data-testid="ns-dropdown-btn"]').should('contain.text', "No Active Gateway") + cy.get('[data-testid="ns-dropdown-btn"]').click() + cy.get('[data-testid="ns-dropdown-total-gateways"]').should('contain.text', `You have ${totalGateways + 4} Gateways in total`) + }) + + it('Check Gateway button activates the Gateway', () => { + cy.visit(ad.yourProductsPath) + cy.get('[data-testid="ns-dropdown-btn"]').click() + cy.get(`[data-testid="ns-dropdown-item-${gateways["namespace1"].gatewayId + '-' + customId}"]`).click() + cy.get('h1').should('contain.text', gateways["namespace1"].displayName) + + cy.visit(ns.detailPath) + cy.url().should('include', '/manager/gateways/detail') + cy.get('[data-testid="ns-detail-gateway-display-name"]').should('contain.text', gateways["namespace1"].displayName) + }) + + it('Recently used gateways are shown in the dropdown', () => { + cy.visit(ns.listPath) + cy.get(`[data-testid="ns-list-activate-link-${gateways["namespace1"].gatewayId + '-' + customId}"]`).click() + cy.get('[data-testid="ns-dropdown-btn"]').click() + cy.get(`[data-testid="ns-dropdown-item-${gateways["namespace2"].gatewayId + '-' + customId}"]`).click() + cy.get('[data-testid="ns-dropdown-btn"]').click() + cy.get('[data-testid="ns-dropdown-heading"]').should('contain.text', "Recently viewed") + cy.get(`[data-testid="ns-dropdown-item-${gateways["namespace1"].gatewayId + '-' + customId}"]`).should('exist') + cy.get(`[data-testid="ns-dropdown-item-${gateways["namespace2"].gatewayId + '-' + customId}"]`).should('not.exist') + cy.get(`[data-testid="ns-dropdown-item-${gateways["namespace3"].gatewayId + '-' + customId}"]`).should('not.exist') + }) + + it('Test search - find results', () => { + cy.visit(ns.listPath) + cy.get(`[data-testid="ns-list-activate-link-${gateways["namespace2"].gatewayId + '-' + customId}"]`).click() + cy.visit(ns.listPath) + cy.get(`[data-testid="ns-list-activate-link-${gateways["namespace3"].gatewayId + '-' + customId}"]`).click() + + cy.get('[data-testid="ns-dropdown-btn"]').click() + cy.get('[data-testid="ns-dropdown-search-input"]').type(gateway1.displayName) + cy.get(`[data-testid="ns-dropdown-item-${gateways["namespace1"].gatewayId + '-' + customId}"]`).should('exist') + cy.get(`[data-testid="ns-dropdown-item-${gateways["namespace2"].gatewayId + '-' + customId}"]`).should('not.exist') + cy.get(`[data-testid="ns-dropdown-item-${gateways["namespace3"].gatewayId + '-' + customId}"]`).should('not.exist') + cy.get('[data-testid="ns-dropdown-search-input"]').clear() + cy.get(`[data-testid="ns-dropdown-item-${gateways["namespace2"].gatewayId + '-' + customId}"]`).should('exist') + }) + + it('Test filter - do not find results', () => { + cy.visit(ns.listPath) + cy.get(`[data-testid="ns-list-activate-link-${gateways["namespace1"].gatewayId + '-' + customId}"]`).click() + + cy.get('[data-testid="ns-dropdown-btn"]').click() + cy.get('[data-testid="ns-dropdown-search-input"]').type('gibberish') + cy.get(`[data-testid="ns-dropdown-item-${gateways["namespace1"].gatewayId + '-' + customId}"]`).should('not.exist') + cy.get('[data-testid="ns-dropdown-no-results-box"]').should('exist') + }) + + it('Cleanup: delete namespaces', () => { + Cypress._.forEach(gateways, (gateway) => { + cy.deleteGatewayCli(gateway.gatewayId + '-' + customId, false) + }); + }) + + after(() => { + // cy.logout() + cy.clearLocalStorage({ log: true }) + cy.deleteAllCookies() + }) + +}) \ No newline at end of file diff --git a/e2e/test-data-dependencies.md b/e2e/test-data-dependencies.md index b813512a5..06314847e 100644 --- a/e2e/test-data-dependencies.md +++ b/e2e/test-data-dependencies.md @@ -1,6 +1,7 @@ | Test | Dependencies | | ----------------------------------------------------------- | -------------------------------------------------------------- | | 01-api-key | | +| │   00-get-started.cy.ts | Must run without existing namespaces| | │   01-create-api.cy.ts | NA | | │   02-team-access.cy.ts | 1.1 | | │   03-request-access-inactive-env.cy.ts | 1.1 to 1.2 | @@ -116,4 +117,7 @@ | |   04-products.cy.ts | ? | | |   05-issuers.cy.ts | ? | | |   06-identifiers.cy.ts | ? | -| |   07-endpoints.cy.ts | ? | \ No newline at end of file +| |   07-endpoints.cy.ts | ? | +| 20-gateways | | +| |   01-list.cy.ts | NA | +| |   02-create.cy.ts | NA | \ No newline at end of file diff --git a/src/nextapp/components/gateway-get-started/gateway-get-started.tsx b/src/nextapp/components/gateway-get-started/gateway-get-started.tsx index 4583575a5..6e50203df 100644 --- a/src/nextapp/components/gateway-get-started/gateway-get-started.tsx +++ b/src/nextapp/components/gateway-get-started/gateway-get-started.tsx @@ -65,7 +65,7 @@ const GatewayGetStarted: React.FC = ({ <> {!hasNamespaces && ( - + = ({ const handleNamespaceChange = React.useCallback( (namespace: Namespace) => async () => { toast({ - title: `Switching to gateway: ${namespace.displayName}`, + title: `Switching to Gateway: ${namespace.displayName}`, status: 'info', isClosable: true, }); @@ -89,14 +89,14 @@ const NamespaceMenu: React.FC = ({ toast.closeAll(); client.invalidateQueries(); toast({ - title: `Switched to gateway: ${namespace.displayName}`, + title: `Switched to Gateway: ${namespace.displayName}`, status: 'success', isClosable: true, }); } catch (err) { toast.closeAll(); toast({ - title: 'Unable to switch gateways', + title: 'Unable to switch Gateways', status: 'error', isClosable: true, }); @@ -148,14 +148,14 @@ const NamespaceMenu: React.FC = ({ event.currentTarget.focus()} onChange={handleSearchChange} value={search} - data-testid="namespace-search-input" + data-testid="ns-dropdown-search-input" /> - {isLoading && Loading gateways...} + {isLoading && Loading Gateways...} {isError && ( Gateways Failed to Load )} @@ -167,6 +167,7 @@ const NamespaceMenu: React.FC = ({ pb={2} m={0} ml={5} + data-testid="ns-dropdown-heading" // @ts-ignore - need bold font in title title={ search !== '' @@ -186,7 +187,8 @@ const NamespaceMenu: React.FC = ({ } > {(search !== '' && namespaceSearchResults.length === 0) && ( - + = ({ - - {`You have ${data.allNamespaces.length} gateway${ + + {`You have ${data.allNamespaces.length} Gateway${ data.allNamespaces.length !== 1 ? 's' : '' } in total`} @@ -237,12 +239,17 @@ const NamespaceMenu: React.FC = ({ > Go to the{' '} - full gateways list + full Gateways list )} + {isSuccess && data.allNamespaces.length === 0 && ( + + No Gateways found + + )} diff --git a/src/nextapp/pages/manager/gateways/detail.tsx b/src/nextapp/pages/manager/gateways/detail.tsx index c245243b0..1bf000e92 100644 --- a/src/nextapp/pages/manager/gateways/detail.tsx +++ b/src/nextapp/pages/manager/gateways/detail.tsx @@ -159,7 +159,6 @@ const NamespacesPage: React.FC = () => { }; }, [namespace]); const handleDelete = React.useCallback(async () => { - console.log('handle delete from detail'); if (user?.namespace) { toast({ title: `Deleting gateway: ${namespace.data?.currentNamespace?.displayName}`, diff --git a/src/nextapp/pages/manager/gateways/get-started.tsx b/src/nextapp/pages/manager/gateways/get-started.tsx index 5ed4b27c2..7d63ac125 100644 --- a/src/nextapp/pages/manager/gateways/get-started.tsx +++ b/src/nextapp/pages/manager/gateways/get-started.tsx @@ -33,7 +33,8 @@ const NamespacesPage: React.FC = () => { {isError && Gateways Failed to Load} {isSuccess && data.allNamespaces.length != 0 && ( - + diff --git a/src/nextapp/pages/manager/gateways/list.tsx b/src/nextapp/pages/manager/gateways/list.tsx index 051508c85..b0a6246a8 100644 --- a/src/nextapp/pages/manager/gateways/list.tsx +++ b/src/nextapp/pages/manager/gateways/list.tsx @@ -248,6 +248,7 @@ const MyGatewaysPage: React.FC = () => { mr={4} placeholder="Filter by: All" onChange={handleFilterChange} + data-testid="ns-filter-select" > @@ -258,7 +259,7 @@ const MyGatewaysPage: React.FC = () => { onBlur={(event) => event.currentTarget.focus()} onChange={handleSearchChange} value={search} - data-testid="namespace-search-input" + data-testid="ns-search-input" /> {isSuccess && @@ -287,6 +288,7 @@ const MyGatewaysPage: React.FC = () => { px={5} py={2} mb={4} + data-testid={`ns-list-item-${namespace.name}`} > @@ -302,6 +304,7 @@ const MyGatewaysPage: React.FC = () => { color="bc-blue" mr={2} onClick={handleNamespaceChange(namespace)} + data-testid={`ns-list-activate-link-${namespace.name}`} > {namespace.displayName} @@ -345,7 +348,7 @@ const MyGatewaysPage: React.FC = () => { justifyContent="center" py={2} > - + No results found