diff --git a/.github/workflows/aps-cypress-e2e.yaml b/.github/workflows/aps-cypress-e2e.yaml index 931fe86db..29fbbd260 100644 --- a/.github/workflows/aps-cypress-e2e.yaml +++ b/.github/workflows/aps-cypress-e2e.yaml @@ -2,7 +2,7 @@ name: Build and Deploy Cypress and Execute Tests on: push: - branches: ['util/*automation*'] + branches: ['test'] env: DASHBOARD_PROJECT_ID: ${{ secrets.CY_DASHBOARD_PRJ_ID }} @@ -20,7 +20,7 @@ jobs: steps: - name: Build GWA API Image run: | - git clone https://github.com/bcgov/gwa-api.git -b dev + git clone https://github.com/bcgov/gwa-api.git -b master cd gwa-api/microservices/gatewayApi docker build -t gwa-api:e2e . - name: Checkout Portal diff --git a/.github/workflows/ci-build-deploy.yaml b/.github/workflows/ci-build-deploy.yaml index c3deb6973..48f471ae5 100644 --- a/.github/workflows/ci-build-deploy.yaml +++ b/.github/workflows/ci-build-deploy.yaml @@ -2,7 +2,7 @@ name: Build and Deploy on: push: - branches: [dev, main, feature/*] + branches: [dev, test, main, feature/*] env: REGISTRY: ghcr.io @@ -299,3 +299,42 @@ jobs: OIDC_CLIENT_ID=${{ secrets.OIDC_CLIENT_ID }} \ OIDC_CLIENT_SECRET=${{ secrets.OIDC_CLIENT_SECRET }} \ ./scripts/init.sh + + - name: 'Restart Portal in OCP Test Namespace' + if: github.ref == 'refs/heads/test' + run: | + oc rollout restart deployment/bcgov-aps-portal-generic-api -n ${{ secrets.OPENSHIFT_TEST_NAMESPACE }} + + - name: 'Create Pull Request for Release' + if: github.ref == 'refs/heads/test' + uses: actions/github-script@v6 + with: + script: | + const { repo, owner } = context.repo; + const openPrs = await github.rest.pulls.list({ + owner, + repo, + head: '${{ github.ref_name }}', + base: 'main', + state: 'open' + }) + + if(openPrs.data.length === 0){ + await github.rest.pulls.create({ + title: 'Create Latest Release', + owner, + repo, + head: '${{ github.ref_name }}', + base: 'main', + body: [ + 'This Pull Request is auto-created by [actions/github-script](https://github.com/actions/github-script).', + 'Please update this PR with appropriate labels to target specific type of release' + ].join('\n\n'), + draft: true + }); + } else { + console.log("There is already an open Pull Request in place.\n") + openPrs.data.forEach((item) => { + console.log(item.html_url) + }) + } diff --git a/e2e/cypress/fixtures/api.json b/e2e/cypress/fixtures/api.json index 875a0600c..d6333c26e 100644 --- a/e2e/cypress/fixtures/api.json +++ b/e2e/cypress/fixtures/api.json @@ -1,18 +1,36 @@ { "organization": { "headers": { - "accept": "application/json" + "accept": "application/json", + "content-type": "application/json" }, "endPoint": "ds/api/v2/organizations", - "orgExpectedList": - { - "name": "planning-and-innovation-division", - "title": "Planning and Innovation Division" - }, - "orgName": "ministry-of-health" + "orgExpectedList": { + "name": "planning-and-innovation-division", + "title": "Planning and Innovation Division" + }, + "orgName": "ministry-of-health", + "expectedScope": ["Dataset.Manage", "GroupAccess.Manage", "Namespace.Assign"], + "expectedNamespace": { + "name": "newplatform", + "orgUnit": "planning-and-innovation-division" + }, + "body": { + "name": "ministry-of-health", + "parent": "/ca.bc.gov", + "members": [ + { + "member": { + "username": "janis@idir" + }, + "roles": ["organization-admin"] + } + ] + } }, "documentation": { "endPoint": "ds/api/v2/namespaces/apiplatform/contents", + "getDocumentation_endPoint": "ds/api/v2/documentation", "headers": { "accept": "application/json", "content-type": "application/json" @@ -28,6 +46,26 @@ "tags": ["tag1", "tag2"] } }, + "apiDirectory": { + "endPoint": "ds/api/v2/namespaces", + "orgEndPoint": "ds/api/v2/organizations", + "headers": { + "accept": "application/json", + "content-type": "application/json" + }, + "body": { + "name": "auto-test-product-new", + "license_title": "Open Government Licence - British Columbia", + "security_class": "PUBLIC", + "view_audience": "Government", + "download_audience": "Public", + "notes": "Some notes", + "title": "Dataset for Test API", + "tags": ["gateway", "kong"], + "organization": "ministry-of-health", + "organizationUnit": "planning-and-innovation-division" + } + }, "authorizationProfiles": { "body": { "name": "my-auth-profile", @@ -72,5 +110,12 @@ } ] } + }, + "namespaces": { + "headers": { + "accept": "application/json", + "content-type": "application/json" + }, + "endPoint": "ds/api/v2/namespaces" } } diff --git a/e2e/cypress/fixtures/apiowner.json b/e2e/cypress/fixtures/apiowner.json index b001b1fbf..46dd0b036 100644 --- a/e2e/cypress/fixtures/apiowner.json +++ b/e2e/cypress/fixtures/apiowner.json @@ -189,6 +189,7 @@ } }, "apiTest": { - "namespace": "apiplatform" + "namespace": "apiplatform", + "delete_namespace": "deleteplatform1" } } \ No newline at end of file diff --git a/e2e/cypress/fixtures/state/store.json b/e2e/cypress/fixtures/state/store.json index 6ab642c75..9e26dfeeb 100644 --- a/e2e/cypress/fixtures/state/store.json +++ b/e2e/cypress/fixtures/state/store.json @@ -1,3 +1 @@ -{ - "credentials": "{\"clientId\": \"\", \"clientSecret\": \"\"}" -} \ No newline at end of file +{} \ No newline at end of file diff --git a/e2e/cypress/pageObjects/authProfile.ts b/e2e/cypress/pageObjects/authProfile.ts index 8fdc8fa3e..7098a7a2f 100644 --- a/e2e/cypress/pageObjects/authProfile.ts +++ b/e2e/cypress/pageObjects/authProfile.ts @@ -107,7 +107,6 @@ class AuthorizationProfile { } } cy.get(this.createBtn).click() - cy.wait(2000) if (isCreated === true) cy.get(this.profileTable).contains(authProfile.name).should('exist') else diff --git a/e2e/cypress/pageObjects/myAccess.ts b/e2e/cypress/pageObjects/myAccess.ts index 894181bc5..4c2f2fda2 100644 --- a/e2e/cypress/pageObjects/myAccess.ts +++ b/e2e/cypress/pageObjects/myAccess.ts @@ -15,7 +15,6 @@ class myAccessPage { saveAPIKeyValue(): void { cy.get(this.apiKyeValueTxt).invoke('val').then(($apiKey: any) => { - debugger cy.saveState('apikey', $apiKey) }) cy.get(this.closeRequestAccesss).click() diff --git a/e2e/cypress/pageObjects/namespace.ts b/e2e/cypress/pageObjects/namespace.ts index 45fc62578..549e4e449 100644 --- a/e2e/cypress/pageObjects/namespace.ts +++ b/e2e/cypress/pageObjects/namespace.ts @@ -32,6 +32,11 @@ class NameSpacePage { cy.get(this.serviceAccountsLink).should('exist') cy.get(this.deleteNamespaceLink).should('exist') } + + deleteNamespace(name: string) { + cy.get(this.deleteNamespaceLink).click() + cy.contains('button', 'Yes, Delete').click() + } } export default NameSpacePage \ No newline at end of file diff --git a/e2e/cypress/support/auth-commands.ts b/e2e/cypress/support/auth-commands.ts index 5b84c4591..b7ca12034 100644 --- a/e2e/cypress/support/auth-commands.ts +++ b/e2e/cypress/support/auth-commands.ts @@ -25,10 +25,11 @@ Cypress.Commands.add('login', (username: string, password: string) => { const login = new LoginPage() const home = new HomePage() - cy.get('header').then(($a) => { + cy.get('header').then(($a) => { if ($a.text().includes('Login')) { - debugger + cy.get(login.loginButton).click() + cy.contains('Github').click() const log = Cypress.log({ name: 'Login to Dev', displayName: 'LOGIN_DEV', @@ -40,7 +41,7 @@ Cypress.Commands.add('login', (username: string, password: string) => { cy.get(login.loginSubmitButton).click() } }) - debugger + if (checkElementExists('.alert')) { cy.reload() cy.get(login.usernameInput).click().type(username) @@ -86,6 +87,30 @@ Cypress.Commands.add('resetCredential', (accessRole: string) => { }) }) +Cypress.Commands.add('getUserSessionTokenValue', () => { + const login = new LoginPage() + const home = new HomePage() + const na = new NamespaceAccessPage() + let userSession: string + cy.deleteAllCookies() + cy.visit('/') + cy.reload() + cy.fixture('apiowner').as('apiowner') + cy.preserveCookies() + cy.visit(login.path) + cy.getUserSession().then(() => { + cy.get('@apiowner').then(({ user, apiTest }: any) => { + cy.login(user.credentials.username, user.credentials.password) + cy.log('Logged in!') + home.useNamespace(apiTest.namespace) + cy.get('@login').then(function (xhr: any) { + userSession = xhr.response.headers['x-auth-request-access-token'] + return userSession + }) + }) + }) +}) + Cypress.Commands.add('getSession', () => { cy.log('< Get Session') cy.request({ method: 'GET', url: Cypress.config('baseUrl') + '/admin/session' }).then( @@ -277,7 +302,6 @@ Cypress.Commands.add('setHeaders', (headerValues: any) => { }) Cypress.Commands.add('setRequestBody', (body: any) => { - debugger requestBody = JSON.stringify(body) }) @@ -319,6 +343,12 @@ Cypress.Commands.add('compareJSONObjects', (actualResponse: any, expectedRespons cy.compareJSONObjects(objectValue2[value], objectValue1[value]); } } else { + debugger + if ((expectedResponse[p] == 'true') || (expectedResponse[p] == 'false')) + Boolean(expectedResponse[p]) + if (['organization', 'organizationUnit'].includes(p) && (!indexFlag)) { + response[p] = response[p]['name'] + } if ((response[p] !== expectedResponse[p]) && !(['clientSecret', 'appId'].includes(p))) { cy.log("Different Value ->" + expectedResponse[p]) assert.fail("JSON value mismatch for " + p) diff --git a/e2e/cypress/support/global.d.ts b/e2e/cypress/support/global.d.ts index 182e7a68a..0e5958c0d 100644 --- a/e2e/cypress/support/global.d.ts +++ b/e2e/cypress/support/global.d.ts @@ -56,5 +56,8 @@ declare namespace Cypress { getUserSession(): Chainable> compareJSONObjects(actualResponse: any, expectedResponse:any, indexFlag?: boolean) : Chainable> + + getUserSessionTokenValue(): Chainable> + } } diff --git a/e2e/cypress/tests/05-clear-resources/02-delete-resources.ts b/e2e/cypress/tests/05-clear-resources/02-delete-resources.ts index 97d9cadbf..c55c921d1 100644 --- a/e2e/cypress/tests/05-clear-resources/02-delete-resources.ts +++ b/e2e/cypress/tests/05-clear-resources/02-delete-resources.ts @@ -1,5 +1,6 @@ import HomePage from '../../pageObjects/home' import LoginPage from '../../pageObjects/login' +import NameSpacePage from '../../pageObjects/namespace' import Products from '../../pageObjects/products' import ServiceAccountsPage from '../../pageObjects/serviceAccounts' @@ -8,6 +9,7 @@ describe('Delete created resources', () => { const home = new HomePage() const sa = new ServiceAccountsPage() const pd = new Products() + const ns = new NameSpacePage before(() => { cy.visit('/') @@ -54,7 +56,15 @@ describe('Delete created resources', () => { it('Delete Namespace', () => { cy.get('@apiowner').then(({ deleteResources }: any) => { - home.deleteNamespace(deleteResources.namespace) + cy.visit(ns.path) + ns.deleteNamespace(deleteResources.namespace) }) }) + + // it('Verify that the deleted namespace does not display in namespace list', () => { + // cy.get('@apiowner').then(({ deleteResources }: any) => { + // const flag = home.useNamespace(deleteResources.namespace) + // assert.equal(flag, false) + // }) + // }) }) diff --git a/e2e/cypress/tests/06-aps-api/01-create-api.spec.ts b/e2e/cypress/tests/06-aps-api/01-create-api.spec.ts index 57d6e4b2c..254c987ba 100644 --- a/e2e/cypress/tests/06-aps-api/01-create-api.spec.ts +++ b/e2e/cypress/tests/06-aps-api/01-create-api.spec.ts @@ -31,6 +31,7 @@ describe('Create API Spec', () => { it('creates and activates new namespace', () => { cy.get('@apiowner').then(({ apiTest }: any) => { home.createNamespace(apiTest.namespace) + home.createNamespace(apiTest.delete_namespace) }) }) diff --git a/e2e/cypress/tests/06-aps-api/02-organization.ts b/e2e/cypress/tests/06-aps-api/02-organization.ts index 02960f8ed..679c01293 100644 --- a/e2e/cypress/tests/06-aps-api/02-organization.ts +++ b/e2e/cypress/tests/06-aps-api/02-organization.ts @@ -1,5 +1,10 @@ -describe('API Tests for Organization Manage Control End points', () => { +import HomePage from "../../pageObjects/home" +import LoginPage from "../../pageObjects/login" +let userSession: any +var nameSpace: string +describe('API Tests to verify the Organization details in the response', () => { + beforeEach(() => { cy.preserveCookies() cy.fixture('api').as('api') @@ -40,12 +45,194 @@ describe('Verify /Organization/{Org} end point', () => { it('Get the resource and verify the org Names in the response', () => { cy.get('@api').then(({ organization }: any) => { - cy.makeAPIRequest(organization.endPoint +'/'+ organization.orgName, 'GET').then((response) => { - debugger + cy.makeAPIRequest(organization.endPoint + '/' + organization.orgName, 'GET').then((response) => { expect(response.status).to.be.equal(200) - var expectedResult = organization.orgExpectedList - assert.isTrue(_.isEqual(response.body.orgUnits[0], organization.orgExpectedList)) + assert.isTrue(Cypress._.isEqual(response.body.orgUnits[0], organization.orgExpectedList)) }) }) }) +}) + +describe('Get the user session token', () => { + + const login = new LoginPage() + const home = new HomePage() + + before(() => { + cy.visit('/') + cy.deleteAllCookies() + cy.reload() + // cy.getUserSessionTokenValue() + }) + + beforeEach(() => { + cy.preserveCookies() + cy.fixture('apiowner').as('apiowner') + cy.visit(login.path) + }) + + it('authenticates Janis (api owner) to get the user session token', () => { + cy.getUserSessionTokenValue().then((value) => { + userSession = value + }) + }) + +}) + +describe('Get the Organization Role', () => { + + var response: any + var actualResponse: any = {} + var expectedResponse: any = {} + + beforeEach(() => { + cy.fixture('api').as('api') + }) + + it('Prepare the Request Specification for the API', () => { + cy.get('@api').then(({ organization }: any) => { + cy.setHeaders(organization.headers) + cy.setAuthorizationToken(userSession) + }) + }) + + it('Get the resource and verify the success code in the response', () => { + cy.get('@api').then(({ organization }: any) => { + cy.makeAPIRequest(organization.endPoint + '/' + organization.orgName + '/roles', 'GET').then((res) => { + expect(res.status).to.be.equal(200) + response = res.body + }) + }) + }) + + it('Compare the scope values in response against the expected values', () => { + cy.get('@api').then(({ organization }: any) => { + actualResponse = response.roles[0].permissions[0].scopes + expectedResponse = organization.expectedScope + assert.isTrue(Cypress._.isEqual(actualResponse, expectedResponse)) + }) + }) + +}) + +describe('Get the Namespace associated with the organization', () => { + + var response: any + var actualResponse: any = {} + var expectedResponse: any = {} + + beforeEach(() => { + cy.fixture('api').as('api') + }) + + it('Prepare the Request Specification for the API', () => { + cy.get('@api').then(({ organization }: any) => { + cy.setHeaders(organization.headers) + cy.setAuthorizationToken(userSession) + }) + }) + + it('Get the resource and verify the success code in the response', () => { + cy.get('@api').then(({ organization }: any) => { + cy.makeAPIRequest(organization.endPoint + '/' + organization.orgName + '/namespaces', 'GET').then((res) => { + debugger + expect(res.status).to.be.equal(200) + response = res.body + nameSpace = response[0].name + }) + }) + }) + + it('Compare the Namespace values in response against the expected values', () => { + cy.get('@api').then(({ organization }: any) => { + expectedResponse = organization.expectedNamespace + // assert.isTrue(Cypress._.isEqual(response, expectedResponse)) + cy.compareJSONObjects(response, expectedResponse, true) + }) + }) + +}) + +describe('Delete the Namespace associated with the organization', () => { + + var response: any + var actualResponse: any = {} + var expectedResponse: any = {} + + beforeEach(() => { + cy.fixture('api').as('api') + cy.fixture('apiowner').as('apiowner') + }) + + it('Prepare the Request Specification for the API', () => { + cy.get('@api').then(({ organization }: any) => { + cy.setHeaders(organization.headers) + cy.setAuthorizationToken(userSession) + }) + }) + + it('Delete the namespace associated with the organization, organization unit and verify the success code in the response', () => { + cy.get('@apiowner').then(({ namespace }: any) => { + cy.get('@api').then(({ organization }: any) => { + cy.makeAPIRequest(organization.endPoint + '/' + organization.orgName + '/' + organization.orgExpectedList.name + '/namespaces/' + nameSpace, 'DELETE').then((res) => { + expect(res.status).to.be.equal(200) + response = res.body + }) + }) + }) + }) + + it('Verify that the deleted Namespace is not displayed in Get Call', () => { + cy.get('@api').then(({ organization }: any) => { + cy.makeAPIRequest(organization.endPoint + '/' + organization.orgName + '/namespaces', 'GET').then((res) => { + expect(res.status).to.be.equal(200) + response = res.body + assert.equal(response.findIndex((x: { name: string }) => x.name === nameSpace),-1) + }) + }) + }) +}) + +describe('Add and Get Organization Access', () => { + + var response: any + var actualResponse: any = {} + var expectedResponse: any = {} + + beforeEach(() => { + cy.fixture('api').as('api') + cy.fixture('apiowner').as('apiowner') + }) + + it('Prepare the Request Specification for the API', () => { + cy.get('@api').then(({ organization }: any) => { + cy.setHeaders(organization.headers) + cy.setAuthorizationToken(userSession) + cy.setRequestBody(organization.body) + }) + }) + + it('Add the access of the organization to the specific user and verify the success code in the response', () => { + cy.get('@api').then(({ organization }: any) => { + cy.makeAPIRequest(organization.endPoint + '/' + organization.orgName + '/access', 'PUT').then((res) => { + debugger + expect(res.status).to.be.equal(204) + }) + }) + }) + + it('Get the resource and verify the success code in the response', () => { + cy.get('@api').then(({ organization }: any) => { + cy.makeAPIRequest(organization.endPoint + '/' + organization.orgName + '/access', 'GET').then((res) => { + expect(res.status).to.be.equal(200) + response = res.body + }) + }) + }) + + it('Compare the Namespace values in response against the expected values', () => { + cy.get('@api').then(({ organization }: any) => { + cy.compareJSONObjects(response, organization.body) + }) + }) }) \ No newline at end of file diff --git a/e2e/cypress/tests/06-aps-api/03-documentation.ts b/e2e/cypress/tests/06-aps-api/03-documentation.ts index b2c930188..379357494 100644 --- a/e2e/cypress/tests/06-aps-api/03-documentation.ts +++ b/e2e/cypress/tests/06-aps-api/03-documentation.ts @@ -1,6 +1,6 @@ import HomePage from "../../pageObjects/home" import LoginPage from "../../pageObjects/login" -let userSession: string +let userSession: any let slugValue: string describe('Get the user session token', () => { @@ -21,15 +21,9 @@ describe('Get the user session token', () => { }) it('authenticates Janis (api owner) to get the user session token', () => { - cy.getUserSession().then(() => { - cy.get('@apiowner').then(({ user, apiTest }: any) => { - cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(apiTest.namespace) - cy.get('@login').then(function (xhr: any) { - userSession = xhr.response.headers['x-auth-request-access-token'] - }) - }) - }) + cy.getUserSessionTokenValue().then((value) => { + userSession = value + }) }) }) @@ -82,7 +76,6 @@ describe('API Tests for Fetching documentation', () => { cy.get('@api').then(({ documentation }: any) => { debugger cy.makeAPIRequest(documentation.endPoint, 'GET').then((res) => { - debugger expect(res.status).to.be.equal(200) slugValue = res.body[0].slug response = res.body[0] @@ -124,7 +117,7 @@ describe('API Tests for Deleting documentation', () => { }) }) -describe('API Tests for to verify no value in Get call after deleting document content', () => { +describe('API Tests to verify no value in Get call after deleting document content', () => { const login = new LoginPage() const home = new HomePage() @@ -150,3 +143,68 @@ describe('API Tests for to verify no value in Get call after deleting document c }) }) }) + +describe('API Tests to verify Get documentation content', () => { + + const login = new LoginPage() + const home = new HomePage() + let slugID: string + + beforeEach(() => { + cy.fixture('api').as('api') + }) + + it('Prepare the Request Specification for the API', () => { + cy.get('@api').then(({ documentation }: any) => { + cy.setHeaders(documentation.headers) + cy.setAuthorizationToken(userSession) + cy.setRequestBody(documentation.body) + }) + }) + + it('Prepare the Request Specification for the API', () => { + cy.get('@api').then(({ documentation }: any) => { + cy.setHeaders(documentation.headers) + cy.setAuthorizationToken(userSession) + cy.setRequestBody(documentation.body) + }) + }) + + it('Put the resource and verify the success code in the response', () => { + cy.get('@api').then(({ documentation }: any) => { + cy.makeAPIRequest(documentation.endPoint, 'PUT').then((response) => { + expect(response.status).to.be.equal(200) + }) + }) + }) + + it('Prepare the Request Specification for the API', () => { + cy.get('@api').then(({ documentation }: any) => { + cy.setHeaders(documentation.headers) + cy.setRequestBody(documentation.body) + }) + }) + + it('Verify that document contant is displayed for GET /documentation', () => { + cy.get('@api').then(({ documentation }: any) => { + debugger + cy.makeAPIRequest(documentation.getDocumentation_endPoint, 'GET').then((response) => { + expect(response.status).to.be.equal(200) + expect(response.body[0].title).to.be.equal(documentation.body.title) + expect(response.body[0].description).to.be.equal(documentation.body.description) + slugID = response.body[0].slug + }) + }) + }) + + it('Verify that document contant is fetch by slug ID', () => { + cy.get('@api').then(({ documentation }: any) => { + debugger + cy.makeAPIRequest(documentation.getDocumentation_endPoint+'/'+slugID, 'GET').then((response) => { + expect(response.status).to.be.equal(200) + expect(response.body.slug).to.be.equal(slugID) + expect(response.body.title).to.be.equal(documentation.body.title) + }) + }) + }) +}) diff --git a/e2e/cypress/tests/06-aps-api/04-authorizationProfiles.ts b/e2e/cypress/tests/06-aps-api/04-authorizationProfiles.ts index fd6330982..0ac1dc0d1 100644 --- a/e2e/cypress/tests/06-aps-api/04-authorizationProfiles.ts +++ b/e2e/cypress/tests/06-aps-api/04-authorizationProfiles.ts @@ -1,6 +1,6 @@ import HomePage from "../../pageObjects/home" import LoginPage from "../../pageObjects/login" -let userSession: string +let userSession: any let testData = require("../../fixtures/test_data/authorizationProfile.json") describe('Get the user session token', () => { @@ -21,15 +21,9 @@ describe('Get the user session token', () => { }) it('authenticates Janis (api owner) to get the user session token', () => { - cy.getUserSession().then(() => { - cy.get('@apiowner').then(({ user, apiTest }: any) => { - cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(apiTest.namespace) - cy.get('@login').then(function (xhr: any) { - userSession = xhr.response.headers['x-auth-request-access-token'] - }) - }) - }) + cy.getUserSessionTokenValue().then((value) => { + userSession = value + }) }) }) diff --git a/e2e/cypress/tests/06-aps-api/05-products.ts b/e2e/cypress/tests/06-aps-api/05-products.ts index e767f6fe7..3fcde5cc9 100644 --- a/e2e/cypress/tests/06-aps-api/05-products.ts +++ b/e2e/cypress/tests/06-aps-api/05-products.ts @@ -1,7 +1,7 @@ import HomePage from "../../pageObjects/home" import LoginPage from "../../pageObjects/login" import Products from "../../pageObjects/products" -let userSession: string +let userSession: any let productID: string let envID: string @@ -100,15 +100,9 @@ describe('Verify that created Product is displayed in UI', () => { }) it('authenticates Janis (api owner) to get the user session token', () => { - cy.getUserSession().then(() => { - cy.get('@apiowner').then(({ user, apiTest }: any) => { - cy.login(user.credentials.username, user.credentials.password) - home.useNamespace(apiTest.namespace) - cy.get('@login').then(function (xhr: any) { - userSession = xhr.response.headers['x-auth-request-access-token'] - }) - }) - }) + cy.getUserSessionTokenValue().then((value) => { + userSession = value + }) }) it('Verify that the product is visible in Manage Product Page', () => { diff --git a/e2e/cypress/tests/06-aps-api/06-namespaces.ts b/e2e/cypress/tests/06-aps-api/06-namespaces.ts new file mode 100644 index 000000000..8663ea10a --- /dev/null +++ b/e2e/cypress/tests/06-aps-api/06-namespaces.ts @@ -0,0 +1,159 @@ +import HomePage from "../../pageObjects/home" +import LoginPage from "../../pageObjects/login" +let userSession: any +let nameSpace: string + +describe('Get the user session token to pass it as authorization token to make the API call ', () => { + + const login = new LoginPage() + const home = new HomePage() + + before(() => { + cy.visit('/') + cy.deleteAllCookies() + cy.reload() + }) + + 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().then((value) => { + userSession = value + }) + nameSpace = apiTest.namespace + }) + }) +}) + +describe('API Tests for Namespace Report', () => { + + const login = new LoginPage() + const home = new HomePage() + var response: any + + beforeEach(() => { + cy.fixture('api').as('api') + }) + + it('Prepare the Request Specification for the API', () => { + cy.get('@api').then(({ namespaces }: any) => { + cy.setHeaders(namespaces.headers) + cy.setAuthorizationToken(userSession) + }) + }) + + it('Get the resource and verify the success code in the response', () => { + cy.get('@api').then(({ namespaces }: any) => { + cy.makeAPIRequest(namespaces.endPoint + "/report", 'GET').then((response) => { + expect(response.status).to.be.equal(200) + }) + }) + }) +}) + +describe('API Tests for Namespace List', () => { + + const login = new LoginPage() + const home = new HomePage() + var response: any + + beforeEach(() => { + cy.fixture('api').as('api') + }) + + it('Prepare the Request Specification for the API', () => { + cy.get('@api').then(({ namespaces }: any) => { + cy.setHeaders(namespaces.headers) + cy.setAuthorizationToken(userSession) + }) + }) + + it('Get the resource and verify the success code in the response', () => { + cy.get('@api').then(({ namespaces }: any) => { + cy.makeAPIRequest(namespaces.endPoint, 'GET').then((res) => { + expect(res.status).to.be.equal(200) + response = res.body + }) + }) + }) + + it('Verify that the selected Namespace is displayed in the Response list in the response', () => { + expect(response).to.be.contain(nameSpace) + }) +}) + +describe('API Tests for Namespace Activities', () => { + + const login = new LoginPage() + const home = new HomePage() + var response: any + + beforeEach(() => { + cy.fixture('api').as('api') + }) + + it('Prepare the Request Specification for the API', () => { + cy.get('@api').then(({ namespaces }: any) => { + cy.setHeaders(namespaces.headers) + cy.setAuthorizationToken(userSession) + }) + }) + + it('Get the resource and verify the success code in the response', () => { + cy.get('@api').then(({ namespaces }: any) => { + cy.makeAPIRequest(namespaces.endPoint + "/" + nameSpace + "/activity", 'GET').then((res) => { + expect(res.status).to.be.equal(200) + }) + }) + }) +}) + +describe('API Tests for Deleting Namespace', () => { + + const login = new LoginPage() + const home = new HomePage() + + + beforeEach(() => { + cy.fixture('api').as('api') + cy.fixture('apiowner').as('apiowner') + }) + + it('Prepare the Request Specification for the API', () => { + cy.get('@api').then(({ namespaces }: any) => { + cy.setHeaders(namespaces.headers) + cy.setAuthorizationToken(userSession) + }) + }) + + it('Delete the namespace and verify the success code in the response', () => { + cy.get('@apiowner').then(({ apiTest }: any) => { + cy.get('@api').then(({ namespaces }: any) => { + cy.makeAPIRequest(namespaces.endPoint + "/" + apiTest.delete_namespace, 'DELETE').then((res) => { + expect(res.status).to.be.equal(200) + }) + }) + }) + }) + + it('Verify that deleted namespace does not display in Get namespace list', () => { + let response : any + let namespace : string + cy.get('@apiowner').then(({ apiTest }: any) => { + namespace = apiTest.delete_namespace + cy.get('@api').then(({ namespaces }: any) => { + cy.makeAPIRequest(namespaces.endPoint, 'GET').then((res) => { + debugger + expect(res.status).to.be.equal(200) + response = res.body + expect(response).to.not.contain(namespace) + }) + }) + }) + }) +}) diff --git a/e2e/cypress/tests/06-aps-api/07-api-directory.ts b/e2e/cypress/tests/06-aps-api/07-api-directory.ts new file mode 100644 index 000000000..5a5f10fb3 --- /dev/null +++ b/e2e/cypress/tests/06-aps-api/07-api-directory.ts @@ -0,0 +1,112 @@ +import HomePage from "../../pageObjects/home" +import LoginPage from "../../pageObjects/login" +let userSession: any +let slugValue: string + +describe('Get the user session token', () => { + + const login = new LoginPage() + const home = new HomePage() + + before(() => { + cy.visit('/') + cy.deleteAllCookies() + cy.reload() + }) + + beforeEach(() => { + cy.preserveCookies() + cy.fixture('apiowner').as('apiowner') + cy.visit(login.path) + }) + + it('authenticates Janis (api owner) to get the user session token', () => { + cy.getUserSessionTokenValue().then((value) => { + userSession = value + }) + }) +}) + +describe('API Tests for Updating dataset', () => { + + const login = new LoginPage() + const home = new HomePage() + var response: any + + beforeEach(() => { + cy.fixture('api').as('api') + cy.fixture('apiowner').as('apiowner') + }) + + it('Prepare the Request Specification for the API', () => { + cy.get('@api').then(({ apiDirectory }: any) => { + cy.setHeaders(apiDirectory.headers) + cy.setAuthorizationToken(userSession) + cy.setRequestBody(apiDirectory.body) + }) + }) + + it('Put the resource and verify the success code in the response', () => { + cy.get('@apiowner').then(({ apiTest }: any) => { + cy.get('@api').then(({ apiDirectory }: any) => { + cy.makeAPIRequest(apiDirectory.endPoint +'/'+ apiTest.namespace + '/datasets', 'PUT').then((response) => { + expect(response.status).to.be.equal(200) + }) + }) + }) + }) + + it('Get the resource (/namespaces/{ns}/datasets/{name}) and verify the success code in the response', () => { + cy.get('@apiowner').then(({ apiTest }: any) => { + cy.get('@api').then(({ apiDirectory }: any) => { + cy.makeAPIRequest(apiDirectory.endPoint +'/'+ apiTest.namespace + '/datasets/'+apiDirectory.body.name, 'GET').then((res) => { + expect(res.status).to.be.equal(200) + response = res.body + }) + }) + }) + }) + + it('Compare the values in response against the values passed in the request', () => { + cy.get('@api').then(({ apiDirectory }: any) => { + debugger + cy.compareJSONObjects(response, apiDirectory.body) + }) + }) + + it('Get the resource (/organizations/{org}/datasets/{name}) and verify the success code in the response', () => { + cy.get('@apiowner').then(({ apiTest }: any) => { + cy.get('@api').then(({ apiDirectory, organization }: any) => { + cy.makeAPIRequest(apiDirectory.orgEndPoint+'/'+organization.orgName + '/datasets/'+apiDirectory.body.name, 'GET').then((res) => { + expect(res.status).to.be.equal(200) + response = res.body + }) + }) + }) + }) + + it('Compare the values in response against the values passed in the request', () => { + cy.get('@api').then(({ apiDirectory }: any) => { + debugger + cy.compareJSONObjects(response, apiDirectory.body) + }) + }) + + it('Get the resource (/organizations/{org}/datasets) and verify the success code in the response', () => { + cy.get('@apiowner').then(({ apiTest }: any) => { + cy.get('@api').then(({ apiDirectory, organization }: any) => { + cy.makeAPIRequest(apiDirectory.orgEndPoint+'/'+organization.orgName + '/datasets/', 'GET').then((res) => { + expect(res.status).to.be.equal(200) + response = res.body + }) + }) + }) + }) + + it('Compare the values in response against the values passed in the request', () => { + cy.get('@api').then(({ apiDirectory }: any) => { + debugger + cy.compareJSONObjects(response, apiDirectory.body, true) + }) + }) +}) \ No newline at end of file diff --git a/e2e/package.json b/e2e/package.json index ae5aace0b..c19f777ed 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -13,7 +13,7 @@ "cy:chromium": "cypress open --browser /usr/bin/chromium", "cy:edge": "npx cypress run --browser edge", "moch:html": "marge results/bcgov-aps-e2e-report.json --reportDir results/report -i -t \"API Services Portal E2E Test Report\"", - "cy:chrome": "cypress run --env TransferProtocol='http' --browser chrome", + "cy:chrome": "cypress run --headed --env TransferProtocol='http' --browser chrome", "cy:run:html": "run-s --continue-on-error cy:run moch:json moch:html", "cy:run:rcd:html": "run-s --continue-on-error cy:run:rcd moch:json moch:html", "cy:chrome:headless": "cypress run --env TransferProtocol='http' --browser chrome --headless", diff --git a/local/gwa-api/entrypoint.sh b/local/gwa-api/entrypoint.sh index fc44bc97b..50675fdb0 100755 --- a/local/gwa-api/entrypoint.sh +++ b/local/gwa-api/entrypoint.sh @@ -45,7 +45,8 @@ cat > "${CONFIG_PATH:-./config/default.json}" < { - const noauthContext = context.createContext({ - skipAccessControl: true, - }); - const prodEnv = await lookupProductEnvironmentServicesBySlug( - noauthContext, - process.env.GWA_PROD_ENV_SLUG - ); - const envCtx = await getEnvironmentContext( - context, - prodEnv.id, - access - ); - - const resourceIds = await getResourceSets(envCtx); - - const resourcesApi = new UMAResourceRegistrationService( - envCtx.uma2.resource_registration_endpoint, - envCtx.accessToken - ); - - const namespaces = await resourcesApi.listResourcesByIdList( - resourceIds - ); - const nsResource = namespaces.filter( - (ns) => ns.name === args.namespace - ); - assert.strictEqual(nsResource.length, 1, 'Invalid Namespace'); - - resourcesApi.deleteResourceSet(nsResource[0].id); - return true; - }, - access: EnforcementPoint, - }, - { schema: 'forceDeleteNamespace(namespace: String!, force: Boolean!): Boolean', diff --git a/src/nextapp/components/consumer-authz/types/acl.tsx b/src/nextapp/components/consumer-authz/types/acl.tsx index 16b6e0e85..4719f5bad 100644 --- a/src/nextapp/components/consumer-authz/types/acl.tsx +++ b/src/nextapp/components/consumer-authz/types/acl.tsx @@ -45,7 +45,7 @@ const ConsumerACL: React.FC = ({ } catch (err) { toast({ title: 'ACL update failed', - description: err?.message, + description: Array.isArray(err) ? err[0].message : err?.message, status: 'error', }); } diff --git a/src/nextapp/components/consumer-authz/types/roles.tsx b/src/nextapp/components/consumer-authz/types/roles.tsx index 0195509ef..2f10127e3 100644 --- a/src/nextapp/components/consumer-authz/types/roles.tsx +++ b/src/nextapp/components/consumer-authz/types/roles.tsx @@ -88,7 +88,7 @@ const RolesComponent: React.FC = ({ } catch (err) { toast({ title: 'Role update failed', - description: err?.message, + description: Array.isArray(err) ? err[0].message : err?.message, status: 'error', }); setBusy.off(); diff --git a/src/nextapp/components/consumer-authz/types/scopes.tsx b/src/nextapp/components/consumer-authz/types/scopes.tsx index cf0a2e24b..eae3f01fe 100644 --- a/src/nextapp/components/consumer-authz/types/scopes.tsx +++ b/src/nextapp/components/consumer-authz/types/scopes.tsx @@ -88,7 +88,7 @@ const ScopesComponent: React.FC = ({ } catch (err) { toast({ title: 'Scope update failed', - description: err?.message, + description: Array.isArray(err) ? err[0].message : err?.message, status: 'error', }); setBusy.off(); diff --git a/src/nextapp/components/request-actions/request-actions.tsx b/src/nextapp/components/request-actions/request-actions.tsx index 1bd521d9b..18bfd2fd2 100644 --- a/src/nextapp/components/request-actions/request-actions.tsx +++ b/src/nextapp/components/request-actions/request-actions.tsx @@ -36,6 +36,7 @@ const RequestActions: React.FC = ({ toast({ title: 'Reject failed', status: 'error', + description: Array.isArray(err) ? err[0].message : err?.message, }); } }; @@ -52,6 +53,7 @@ const RequestActions: React.FC = ({ toast({ title: 'Approval failed', status: 'error', + description: Array.isArray(err) ? err[0].message : err?.message, }); } }; diff --git a/src/nextapp/pages/manager/namespaces/index.tsx b/src/nextapp/pages/manager/namespaces/index.tsx index ac4464da8..5a55f3c76 100644 --- a/src/nextapp/pages/manager/namespaces/index.tsx +++ b/src/nextapp/pages/manager/namespaces/index.tsx @@ -312,6 +312,6 @@ export default NamespacesPage; const mutation = gql` mutation DeleteNamespace($name: String!) { - deleteNamespace(namespace: $name) + forceDeleteNamespace(namespace: $name, force: false) } `; diff --git a/src/nextapp/shared/types/query.types.ts b/src/nextapp/shared/types/query.types.ts index aabb77209..12238495d 100644 --- a/src/nextapp/shared/types/query.types.ts +++ b/src/nextapp/shared/types/query.types.ts @@ -7458,7 +7458,6 @@ export type Mutation = { updateConsumerScopeAssignment?: Maybe; regenerateCredentials?: Maybe; createNamespace?: Maybe; - deleteNamespace?: Maybe; forceDeleteNamespace?: Maybe; createServiceAccount?: Maybe; createUmaPolicy?: Maybe; @@ -8256,11 +8255,6 @@ export type MutationCreateNamespaceArgs = { }; -export type MutationDeleteNamespaceArgs = { - namespace: Scalars['String']; -}; - - export type MutationForceDeleteNamespaceArgs = { namespace: Scalars['String']; force: Scalars['Boolean']; diff --git a/src/services/checkStatus.ts b/src/services/checkStatus.ts index 463d0fe8d..99b1acf80 100644 --- a/src/services/checkStatus.ts +++ b/src/services/checkStatus.ts @@ -1,22 +1,26 @@ -import { logger } from '../logger' +import { logger } from '../logger'; -import { IssuerMisconfigError } from './issuerMisconfigError' +import { IssuerMisconfigError } from './issuerMisconfigError'; -export function checkStatus(res: any) { - if (res.ok) { - return res; - } else { - const error = { reason: 'unknown_error', status: `${res.status} ${res.statusText}` } - logger.error("Error - %d %s", res.status, res.statusText) - res.text().then((t : string ) => { - logger.error("ERROR " + t) - try { - const errors = JSON.parse(t) - error['reason'] = errors['error'] - } catch (e) { - logger.error("Not able to parse error response (%s)", e) - } - }) - throw new IssuerMisconfigError(error); +export async function checkStatus(res: any) { + if (res.ok) { + return res; + } else { + const error = { + reason: 'unknown_error', + status: `${res.status} ${res.statusText}`, + }; + logger.error('Error - %d %s', res.status, res.statusText); + const body = await res.text(); + + logger.error('ERROR ' + body); + try { + const errors = JSON.parse(body); + error['reason'] = errors['error']; + logger.error('Added reason to error: %j', error); + } catch (e) { + logger.error('Not able to parse error response (%s)', e); } + throw new IssuerMisconfigError(error); + } } diff --git a/src/services/issuerMisconfigError.ts b/src/services/issuerMisconfigError.ts index 2830311ab..35273d76f 100644 --- a/src/services/issuerMisconfigError.ts +++ b/src/services/issuerMisconfigError.ts @@ -6,6 +6,6 @@ export class IssuerMisconfigError extends Error { Error.captureStackTrace(this, this.constructor); this.name = this.constructor.name; - this.errors = message; + this.errors = [message]; } } diff --git a/src/services/keycloak/client-registration-service.ts b/src/services/keycloak/client-registration-service.ts index eab6c7d91..0430930e6 100644 --- a/src/services/keycloak/client-registration-service.ts +++ b/src/services/keycloak/client-registration-service.ts @@ -192,10 +192,7 @@ export class KeycloakClientRegistrationService { .map((sname) => scopeToId[sname]); if (scopesToAdd.filter((s: any) => s == null).length != 0) { throw Error( - 'Missing one of these Realm Defaults - ' + - (optional ? 'Optional' : 'Default') + - ' Scopes: ' + - desiredSetOfScopes + 'One or more scopes missing from IdP - ' + desiredSetOfScopes ); } diff --git a/src/services/keystone/activity.ts b/src/services/keystone/activity.ts index a9e0ba00c..420ec6b9e 100644 --- a/src/services/keystone/activity.ts +++ b/src/services/keystone/activity.ts @@ -106,10 +106,13 @@ export async function recordActivity( refId: string, message: string, result: string = '', - activityContext: string = '' + activityContext: string = '', + productNamespace: string = undefined ) { const userId = context.authedItem.userId; - const namespace = context.authedItem.namespace; + const namespace = productNamespace + ? productNamespace + : context.authedItem.namespace; const name = `${action} ${type}[${refId}]`; logger.debug('[recordActivity] userid=%s name=%s', userId, name); diff --git a/src/services/keystone/application.ts b/src/services/keystone/application.ts index 72b20ede6..d09b507d7 100644 --- a/src/services/keystone/application.ts +++ b/src/services/keystone/application.ts @@ -1,18 +1,39 @@ -import { Logger } from "../../logger" -import { Application } from "./types" +import { Logger } from '../../logger'; +import { Application } from './types'; -const logger = Logger('keystone.application') +const logger = Logger('keystone.application'); -export async function lookupApplication (context: any, id: string) : Promise { - const result = await context.executeGraphQL({ - query: `query GetApplicationById($id: ID!) { +export async function lookupApplication( + context: any, + id: string +): Promise { + const result = await context.executeGraphQL({ + query: `query GetApplicationById($id: ID!) { allApplications(where: {id: $id}) { id appId } }`, - variables: { id: id }, - }) - logger.debug("[lookupApplication] result %j", result) - return result.data.allApplications[0] + variables: { id }, + }); + logger.debug('[lookupApplication] result %j', result); + return result.data.allApplications[0]; +} + +export async function lookupMyApplicationsById( + context: any, + id: string +): Promise { + logger.debug('[lookupMyApplicationsById] %s', id); + const result = await context.executeGraphQL({ + query: `query GetApplicationByAppId($id: ID!) { + myApplications(where: {id: $id}) { + id + appId + } + }`, + variables: { id }, + }); + logger.debug('[lookupMyApplicationsById] result %j', result); + return result.data.myApplications[0]; } diff --git a/src/services/keystone/gateway-consumer.ts b/src/services/keystone/gateway-consumer.ts index bc3a10b0c..36a1c3cf3 100644 --- a/src/services/keystone/gateway-consumer.ts +++ b/src/services/keystone/gateway-consumer.ts @@ -93,7 +93,8 @@ export async function lookupKongConsumerIdByName( export async function lookupKongConsumerByCustomId( context: any, - name: string + name: string, + expected: boolean = true ): Promise { assert.strictEqual( name != null && typeof name != 'undefined' && name != '', @@ -112,11 +113,13 @@ export async function lookupKongConsumerByCustomId( logger.debug('Query [lookupKongConsumerByCustomId] result %j', result); assert.strictEqual( - result.data.allGatewayConsumers.length, - 1, + expected === false || result.data.allGatewayConsumers.length === 1, + true, 'Unexpected data returned for Consumer lookup' ); - return result.data.allGatewayConsumers[0]; + return result.data.allGatewayConsumers.length === 0 + ? undefined + : result.data.allGatewayConsumers[0]; } export async function lookupKongConsumerByUsername( diff --git a/src/services/keystone/index.ts b/src/services/keystone/index.ts index 88473a389..9f1a4e42c 100644 --- a/src/services/keystone/index.ts +++ b/src/services/keystone/index.ts @@ -8,7 +8,7 @@ export { export { recordActivity, recordActivityWithBlob } from './activity'; -export { lookupApplication } from './application'; +export { lookupApplication, lookupMyApplicationsById } from './application'; export { deleteRecord, deleteRecords } from './common-delete-record'; diff --git a/src/services/keystone/product-environment.ts b/src/services/keystone/product-environment.ts index aa72de1c9..2f3d397e5 100644 --- a/src/services/keystone/product-environment.ts +++ b/src/services/keystone/product-environment.ts @@ -205,6 +205,7 @@ export async function lookupEnvironmentAndIssuerById(context: any, id: string) { query: `query GetCredentialIssuerByEnvironmentId($id: ID!) { Environment(where: {id: $id}) { id + appId name active approval diff --git a/src/services/keystone/types.ts b/src/services/keystone/types.ts index aabb77209..12238495d 100644 --- a/src/services/keystone/types.ts +++ b/src/services/keystone/types.ts @@ -7458,7 +7458,6 @@ export type Mutation = { updateConsumerScopeAssignment?: Maybe; regenerateCredentials?: Maybe; createNamespace?: Maybe; - deleteNamespace?: Maybe; forceDeleteNamespace?: Maybe; createServiceAccount?: Maybe; createUmaPolicy?: Maybe; @@ -8256,11 +8255,6 @@ export type MutationCreateNamespaceArgs = { }; -export type MutationDeleteNamespaceArgs = { - namespace: Scalars['String']; -}; - - export type MutationForceDeleteNamespaceArgs = { namespace: Scalars['String']; force: Scalars['Boolean']; diff --git a/src/services/workflow/apply.ts b/src/services/workflow/apply.ts index 3a8c29417..713a5f958 100644 --- a/src/services/workflow/apply.ts +++ b/src/services/workflow/apply.ts @@ -39,12 +39,14 @@ export const Apply = async ( const message = { text: '' }; if (originalInput['credential'] == 'NEW') { - try { - const requestDetails = await lookupEnvironmentAndApplicationByAccessRequest( - context, - existingItem.id - ); + const requestDetails = await lookupEnvironmentAndApplicationByAccessRequest( + context, + existingItem.id + ); + const productNamespace = + requestDetails.productEnvironment.product.namespace; + try { const newCredential = await generateCredential(context, requestDetails); if (newCredential != null) { updatedItem['credential'] = JSON.stringify(newCredential); @@ -97,7 +99,8 @@ export const Apply = async ( updatedItem.id, message.text, 'success', - JSON.stringify(originalInput) + JSON.stringify(originalInput), + productNamespace ); } catch (err) { logger.error('Workflow Error %s', err); @@ -112,7 +115,9 @@ export const Apply = async ( 'AccessRequest', updatedItem.id, 'Failed to Apply Workflow - ' + err, - 'failed' + 'failed', + '', + productNamespace ); throw err; } diff --git a/src/services/workflow/validate-access-request.ts b/src/services/workflow/validate-access-request.ts index 73965c413..25b1dc694 100644 --- a/src/services/workflow/validate-access-request.ts +++ b/src/services/workflow/validate-access-request.ts @@ -5,6 +5,8 @@ import { lookupCredentialIssuerById, lookupUserLegals, LegalAgreed, + lookupKongConsumerByCustomId, + lookupMyApplicationsById, } from '../keystone'; import { IssuerEnvironmentConfig, getIssuerEnvironmentConfig } from './types'; import { isUpdatingToIssued, isRequested, isBlank } from './common'; @@ -89,6 +91,26 @@ export const Validate = async ( true, 'Product not elligible for requesting access' ); + + // assert that the Consumer does not already exist + const application = await lookupMyApplicationsById( + context, + resolvedData.application.toString() + ); + + const clientId = prodEnv.appId + '-' + application.appId; + + const consumer = await lookupKongConsumerByCustomId( + context, + clientId, + false + ); + logger.debug('Does consumer exist? %s : %j', clientId, consumer); + assert.strictEqual( + typeof consumer === 'undefined', + true, + 'This application already has access.' + ); } else if (isUpdatingToIssued(existingItem, resolvedData)) { assert.strictEqual(requestDetails != null, true, errors.WF01); assert.strictEqual( @@ -171,7 +193,7 @@ export const Validate = async ( } } } catch (err) { - logger.error('Validation caused exception %j', err); + logger.error('Validation caused exception %s', err); assert(err instanceof assert.AssertionError); addValidationError(err.message); } diff --git a/src/services/workflow/validate-active-environment.ts b/src/services/workflow/validate-active-environment.ts index 1235ded4a..9d016812a 100644 --- a/src/services/workflow/validate-active-environment.ts +++ b/src/services/workflow/validate-active-environment.ts @@ -161,6 +161,9 @@ export const ValidateActiveEnvironment = async ( '] missing or incomplete acl plugin.' ); } + addValidationError( + 'The kong-acl-only flow can not be enabled on the API Directory' + ); } else if (flow == 'client-credentials') { assert.strictEqual( issuer != null,