From e8ff068dae1aacd8a77570006b4f29c49bb6e5bf Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Mon, 9 Jan 2023 15:31:07 +0000 Subject: [PATCH] Implement logic for automatic setup of api tests --- qa-core/.env | 5 +- .../TestConfiguration/InternalAPIClient.ts | 55 ++++++++++------ .../TestConfiguration/accounts.ts | 28 ++++---- .../TestConfiguration/accountsAPIClient.ts | 64 +++++++++++++++++++ .../internal-api/TestConfiguration/index.ts | 41 ++++++++---- .../config/internal-api/fixtures/accounts.ts | 17 +++-- .../internal-api/fixtures/types/newAccount.ts | 5 ++ qa-core/src/environment.ts | 3 + .../applications/applications.spec.ts | 6 +- .../internal-api/screens/screens.spec.ts | 8 ++- .../tests/internal-api/tables/tables.spec.ts | 6 +- .../userManagement/appSpecificRoles.spec.ts | 43 ++++++++++++- .../userManagement/userManagement.spec.ts | 8 ++- 13 files changed, 224 insertions(+), 65 deletions(-) create mode 100644 qa-core/src/config/internal-api/TestConfiguration/accountsAPIClient.ts create mode 100644 qa-core/src/config/internal-api/fixtures/types/newAccount.ts diff --git a/qa-core/.env b/qa-core/.env index 93b5fde74a..096fb4e157 100644 --- a/qa-core/.env +++ b/qa-core/.env @@ -4,4 +4,7 @@ ENCRYPTED_TEST_PUBLIC_API_KEY=a65722f06bee5caeadc5d7ca2f543a43-d610e627344210c64 COUCH_DB_URL=http://budibase:budibase@localhost:4567 COUCH_DB_USER=budibase COUCH_DB_PASSWORD=budibase -JWT_SECRET=test \ No newline at end of file +JWT_SECRET=test +BUDIBASE_SERVER_URL=http://localhost:4100 +BUDIBASE_HOST= budirelease.live +BUDIBASE_ACCOUNTS_URL=https://account.budirelease.live \ No newline at end of file diff --git a/qa-core/src/config/internal-api/TestConfiguration/InternalAPIClient.ts b/qa-core/src/config/internal-api/TestConfiguration/InternalAPIClient.ts index dafc2b1ff2..33dff83f5b 100644 --- a/qa-core/src/config/internal-api/TestConfiguration/InternalAPIClient.ts +++ b/qa-core/src/config/internal-api/TestConfiguration/InternalAPIClient.ts @@ -11,40 +11,53 @@ interface ApiOptions { class InternalAPIClient { host: string + tenantName?: string appId?: string cookie?: string constructor(appId?: string) { - if (!env.BUDIBASE_SERVER_URL) { + if (!env.BUDIBASE_HOST) { throw new Error("Must set BUDIBASE_SERVER_URL env var") } - this.host = `${env.BUDIBASE_SERVER_URL}/api` + this.host = `${env.BUDIBASE_HOST}/api` this.appId = appId } + setTenantName(tenantName: string) { + this.tenantName = tenantName + } + apiCall = (method: APIMethod) => - async (url = "", options: ApiOptions = {}) => { - const requestOptions = { - method, - body: JSON.stringify(options.body), - headers: { - "x-budibase-app-id": this.appId, - "Content-Type": "application/json", - Accept: "application/json", - cookie: this.cookie, - ...options.headers, - }, - credentials: "include", - } + async (url = "", options: ApiOptions = {}) => { + const requestOptions = { + method, + body: JSON.stringify(options.body), + headers: { + "x-budibase-app-id": this.appId, + "Content-Type": "application/json", + Accept: "application/json", + cookie: this.cookie, + redirect: "follow", + follow: 20, + ...options.headers, + }, + credentials: "include", + } - // @ts-ignore - const response = await fetch(`${this.host}${url}`, requestOptions) - if (response.status !== 200) { - console.error(response) + // @ts-ignore + const response = await fetch(`https://${process.env.TENANT_ID}.${this.host}${url}`, requestOptions) + if (response.status == 404 || response.status == 500) { + console.error("Error in apiCall") + console.error("Response:") + console.error(response) + console.error("Response body:") + console.error(response.body) + console.error("Request body:") + console.error(requestOptions.body) + } + return response } - return response - } post = this.apiCall("POST") get = this.apiCall("GET") diff --git a/qa-core/src/config/internal-api/TestConfiguration/accounts.ts b/qa-core/src/config/internal-api/TestConfiguration/accounts.ts index cb544d18a0..b61b9688ea 100644 --- a/qa-core/src/config/internal-api/TestConfiguration/accounts.ts +++ b/qa-core/src/config/internal-api/TestConfiguration/accounts.ts @@ -1,32 +1,34 @@ import { Response } from "node-fetch" import { Account } from "@budibase/types" -import InternalAPIClient from "./InternalAPIClient" +import AccountsAPIClient from "./accountsAPIClient" +import { NewAccount } from "../fixtures/types/newAccount" export default class AccountsApi { - api: InternalAPIClient + api: AccountsAPIClient - constructor(apiClient: InternalAPIClient) { - this.api = apiClient + constructor(AccountsAPIClient: AccountsAPIClient) { + this.api = AccountsAPIClient } - async validateEmail(email: string): Promise<[Response, any]> { + async validateEmail(email: string): Promise { const response = await this.api.post(`/accounts/validate/email`, { body: { email } }) - const json = await response.json() expect(response).toHaveStatusCode(200) - return [response, json] + return response } - async validateTenantId(tenantId: string): Promise<[Response, any]> { + async validateTenantId(tenantId: string): Promise { const response = await this.api.post(`/accounts/validate/tenantId`, { body: { tenantId } }) - const json = await response.json() expect(response).toHaveStatusCode(200) - return [response, json] + return response } - async create(body: Account): Promise<[Response, Account]> { - const response = await this.api.post(`/accounts`, { body }) + async create(body: Partial): Promise<[Response, Account]> { + const headers = { + 'no-verify': '1' + } + const response = await this.api.post(`/accounts`, { body, headers }) const json = await response.json() - expect(response).toHaveStatusCode(200) + expect(response).toHaveStatusCode(201) return [response, json] } } diff --git a/qa-core/src/config/internal-api/TestConfiguration/accountsAPIClient.ts b/qa-core/src/config/internal-api/TestConfiguration/accountsAPIClient.ts new file mode 100644 index 0000000000..36dfc27ca9 --- /dev/null +++ b/qa-core/src/config/internal-api/TestConfiguration/accountsAPIClient.ts @@ -0,0 +1,64 @@ +import env from "../../../environment" +import fetch, { HeadersInit } from "node-fetch" + +type APIMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" + +interface ApiOptions { + method?: APIMethod + body?: object + headers?: HeadersInit | undefined +} + +class AccountsAPIClient { + host: string + appId?: string + cookie?: string + + constructor(appId?: string) { + if (!env.BUDIBASE_ACCOUNTS_URL) { + throw new Error("Must set BUDIBASE_SERVER_URL env var") + } + this.host = `${env.BUDIBASE_ACCOUNTS_URL}/api` + this.appId = appId + } + + apiCall = + (method: APIMethod) => + async (url = "", options: ApiOptions = {}) => { + const requestOptions = { + method, + body: JSON.stringify(options.body), + headers: { + "x-budibase-app-id": this.appId, + "Content-Type": "application/json", + Accept: "application/json", + cookie: this.cookie, + redirect: "follow", + follow: 20, + ...options.headers, + }, + credentials: "include", + } + + // @ts-ignore + const response = await fetch(`${this.host}${url}`, requestOptions) + if (response.status == 404 || response.status == 500) { + console.error("Error in apiCall") + console.error("Response:") + console.error(response) + console.error("Response body:") + console.error(response.body) + console.error("Request body:") + console.error(requestOptions.body) + } + return response + } + + post = this.apiCall("POST") + get = this.apiCall("GET") + patch = this.apiCall("PATCH") + del = this.apiCall("DELETE") + put = this.apiCall("PUT") +} + +export default AccountsAPIClient diff --git a/qa-core/src/config/internal-api/TestConfiguration/index.ts b/qa-core/src/config/internal-api/TestConfiguration/index.ts index cb5e8cdf7f..9c5e7e7d87 100644 --- a/qa-core/src/config/internal-api/TestConfiguration/index.ts +++ b/qa-core/src/config/internal-api/TestConfiguration/index.ts @@ -1,6 +1,7 @@ import ApplicationApi from "./applications" import AuthApi from "./auth" import InternalAPIClient from "./InternalAPIClient" +import AccountsApiClient from "./accountsAPIClient" import TablesApi from "./tables" import RowApi from "./rows" import ScreenApi from "./screens" @@ -17,15 +18,20 @@ export default class TestConfiguration { rows: RowApi users: UserManagementApi accounts: AccountsApi + apiClient: InternalAPIClient + accountsApiClient: AccountsApiClient - constructor(apiClient: InternalAPIClient) { - this.applications = new ApplicationApi(apiClient) - this.tables = new TablesApi(apiClient) - this.rows = new RowApi(apiClient) - this.auth = new AuthApi(apiClient) - this.screen = new ScreenApi(apiClient) - this.users = new UserManagementApi(apiClient) - this.accounts = new AccountsApi(apiClient) + constructor(apiClient: InternalAPIClient, accountsApiClient: AccountsApiClient) { + this.apiClient = apiClient + this.accountsApiClient = accountsApiClient + + this.applications = new ApplicationApi(this.apiClient) + this.tables = new TablesApi(this.apiClient) + this.rows = new RowApi(this.apiClient) + this.auth = new AuthApi(this.apiClient) + this.screen = new ScreenApi(this.apiClient) + this.users = new UserManagementApi(this.apiClient) + this.accounts = new AccountsApi(this.accountsApiClient) this.context = {} } @@ -35,10 +41,23 @@ export default class TestConfiguration { async setupAccountAndTenant() { const account = generateAccount() - await this.accounts.validateEmail(account.email) - await this.accounts.validateTenantId(account.tenantId) + //await this.accounts.validateEmail(account.email) + //await this.accounts.validateTenantId(account.tenantId) + process.env.TENANT_ID = account.tenantId await this.accounts.create(account) - await this.auth.login(account.email, account.password) + await this.updateApiClients(account.tenantName) + await this.auth.login(account.email, account.password) + } + + async updateApiClients(tenantName: string) { + this.apiClient.setTenantName(tenantName) + this.applications = new ApplicationApi(this.apiClient) + this.tables = new TablesApi(this.apiClient) + this.rows = new RowApi(this.apiClient) + this.auth = new AuthApi(this.apiClient) + this.screen = new ScreenApi(this.apiClient) + this.users = new UserManagementApi(this.apiClient) + this.context = {} } async login(email: string, password: string) { diff --git a/qa-core/src/config/internal-api/fixtures/accounts.ts b/qa-core/src/config/internal-api/fixtures/accounts.ts index 4c1660c0aa..8db1701ffa 100644 --- a/qa-core/src/config/internal-api/fixtures/accounts.ts +++ b/qa-core/src/config/internal-api/fixtures/accounts.ts @@ -1,17 +1,22 @@ -import { Account } from "@budibase/types"; -import generator from "../../generator"; +import { NewAccount } from "./types/newAccount"; -export const generateAccount = (): Account => { +import generator from "../../generator"; +import { Hosting } from "@budibase/types"; + +export const generateAccount = (): Partial => { const randomGuid = generator.guid(); + let tenant: string = 'a' + randomGuid; + tenant = tenant.replace(/-/g, ''); + return { email: `qa+${randomGuid}@budibase.com`, - hosting: "cloud", + hosting: Hosting.CLOUD, name: `qa+${randomGuid}@budibase.com`, password: `${randomGuid}`, profession: "software_engineer", size: "10+", - tenantId: `${randomGuid}`, - tenantName: `${randomGuid}`, + tenantId: `${tenant}`, + tenantName: `${tenant}`, } } diff --git a/qa-core/src/config/internal-api/fixtures/types/newAccount.ts b/qa-core/src/config/internal-api/fixtures/types/newAccount.ts new file mode 100644 index 0000000000..bda204c151 --- /dev/null +++ b/qa-core/src/config/internal-api/fixtures/types/newAccount.ts @@ -0,0 +1,5 @@ +import { Account } from "@budibase/types"; + +export interface NewAccount extends Account { + password: string; +} \ No newline at end of file diff --git a/qa-core/src/environment.ts b/qa-core/src/environment.ts index b0ed3cec85..e8119c3918 100644 --- a/qa-core/src/environment.ts +++ b/qa-core/src/environment.ts @@ -1,6 +1,9 @@ const env = { BUDIBASE_SERVER_URL: process.env.BUDIBASE_SERVER_URL, + BUDIBASE_ACCOUNT_URL: process.env.BUDIBASE_ACCOUNT_URL, BUDIBASE_PUBLIC_API_KEY: process.env.BUDIBASE_PUBLIC_API_KEY, + BUDIBASE_ACCOUNTS_URL: process.env.BUDIBASE_ACCOUNTS_URL, + BUDIBASE_HOST: process.env.BUDIBASE_HOST, _set(key: any, value: any) { process.env[key] = value module.exports[key] = value diff --git a/qa-core/src/tests/internal-api/applications/applications.spec.ts b/qa-core/src/tests/internal-api/applications/applications.spec.ts index 7d889b7e87..67ca737267 100644 --- a/qa-core/src/tests/internal-api/applications/applications.spec.ts +++ b/qa-core/src/tests/internal-api/applications/applications.spec.ts @@ -2,16 +2,18 @@ import TestConfiguration from "../../../config/internal-api/TestConfiguration" import { Application } from "@budibase/server/api/controllers/public/mapping/types" import { db } from "@budibase/backend-core" import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient" +import AccountsAPIClient from "../../../config/internal-api/TestConfiguration/accountsAPIClient" import { generateApp, appFromTemplate } from "../../../config/internal-api/fixtures/applications" import generator from "../../../config/generator" import generateScreen from "../../../config/internal-api/fixtures/screens" describe("Internal API - Application creation, update, publish and delete", () => { const api = new InternalAPIClient() - const config = new TestConfiguration(api) + const accountsAPI = new AccountsAPIClient() + const config = new TestConfiguration(api, accountsAPI) beforeAll(async () => { - await config.loginAsAdmin() + await config.setupAccountAndTenant() }) afterAll(async () => { diff --git a/qa-core/src/tests/internal-api/screens/screens.spec.ts b/qa-core/src/tests/internal-api/screens/screens.spec.ts index 1d2a21a8c7..926a3d6d54 100644 --- a/qa-core/src/tests/internal-api/screens/screens.spec.ts +++ b/qa-core/src/tests/internal-api/screens/screens.spec.ts @@ -1,17 +1,19 @@ import TestConfiguration from "../../../config/internal-api/TestConfiguration" import { App } from "@budibase/types" import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient" +import AccountsAPIClient from "../../../config/internal-api/TestConfiguration/accountsAPIClient" import { generateApp, appFromTemplate } from "../../../config/internal-api/fixtures/applications" import { Screen } from "@budibase/types" import generateScreen from "../../../config/internal-api/fixtures/screens" describe("Internal API - /screens endpoints", () => { const api = new InternalAPIClient() - const config = new TestConfiguration(api) - const appConfig = new TestConfiguration(api) + const accountsAPI = new AccountsAPIClient() + const config = new TestConfiguration(api, accountsAPI) + const appConfig = new TestConfiguration(api, accountsAPI) beforeAll(async () => { - await config.loginAsAdmin() + await config.setupAccountAndTenant() }) afterAll(async () => { diff --git a/qa-core/src/tests/internal-api/tables/tables.spec.ts b/qa-core/src/tests/internal-api/tables/tables.spec.ts index 6b2d2240e5..e5f7cfa964 100644 --- a/qa-core/src/tests/internal-api/tables/tables.spec.ts +++ b/qa-core/src/tests/internal-api/tables/tables.spec.ts @@ -1,6 +1,7 @@ import TestConfiguration from "../../../config/internal-api/TestConfiguration" import { Application } from "@budibase/server/api/controllers/public/mapping/types" import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient" +import AccountsAPIClient from "../../../config/internal-api/TestConfiguration/accountsAPIClient" import generator from "../../../config/generator" import { generateTable, @@ -10,10 +11,11 @@ import { generateNewRowForTable } from "../../../config/internal-api/fixtures/ro describe("Internal API - Application creation, update, publish and delete", () => { const api = new InternalAPIClient() - const config = new TestConfiguration(api) + const accountsAPI = new AccountsAPIClient() + const config = new TestConfiguration(api, accountsAPI) beforeAll(async () => { - await config.loginAsAdmin() + await config.setupAccountAndTenant() }) afterAll(async () => { diff --git a/qa-core/src/tests/internal-api/userManagement/appSpecificRoles.spec.ts b/qa-core/src/tests/internal-api/userManagement/appSpecificRoles.spec.ts index 2447a31558..c524480398 100644 --- a/qa-core/src/tests/internal-api/userManagement/appSpecificRoles.spec.ts +++ b/qa-core/src/tests/internal-api/userManagement/appSpecificRoles.spec.ts @@ -1,6 +1,7 @@ import TestConfiguration from "../../../config/internal-api/TestConfiguration" import { Application } from "@budibase/server/api/controllers/public/mapping/types" import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient" +import AccountsAPIClient from "../../../config/internal-api/TestConfiguration/accountsAPIClient" import { generateApp, appFromTemplate } from "../../../config/internal-api/fixtures/applications" import { generateUser } from "../../../config/internal-api/fixtures/userManagement" import { User } from "@budibase/types" @@ -9,12 +10,16 @@ import generateScreen from "../../../config/internal-api/fixtures/screens" import { db } from "@budibase/backend-core" describe("Internal API - App Specific Roles & Permissions", () => { - const api = new InternalAPIClient() - const config = new TestConfiguration(api) + let api: InternalAPIClient + let accountsAPI: AccountsAPIClient + let config: TestConfiguration // Before each test, login as admin. Some tests will require login as a different user beforeEach(async () => { - await config.loginAsAdmin() + api = new InternalAPIClient() + accountsAPI = new AccountsAPIClient() + config = new TestConfiguration(api, accountsAPI) + await config.setupAccountAndTenant() }) afterAll(async () => { @@ -103,6 +108,22 @@ describe("Internal API - App Specific Roles & Permissions", () => { }) describe("Check Access for default roles", () => { + let api: InternalAPIClient + let accountsAPI: AccountsAPIClient + let config: TestConfiguration + + // Before each test, login as admin. Some tests will require login as a different user + beforeEach(async () => { + api = new InternalAPIClient() + accountsAPI = new AccountsAPIClient() + config = new TestConfiguration(api, accountsAPI) + await config.setupAccountAndTenant() + }) + + afterAll(async () => { + await config.afterAll() + }) + it("Check Table access for app user", async () => { const appUser = generateUser() expect(appUser[0].builder?.global).toEqual(false) @@ -203,6 +224,22 @@ describe("Internal API - App Specific Roles & Permissions", () => { }) describe("Screen Access for App specific roles", () => { + let api: InternalAPIClient + let accountsAPI: AccountsAPIClient + let config: TestConfiguration + + // Before each test, login as admin. Some tests will require login as a different user + beforeEach(async () => { + api = new InternalAPIClient() + accountsAPI = new AccountsAPIClient() + config = new TestConfiguration(api, accountsAPI) + await config.setupAccountAndTenant() + }) + + afterAll(async () => { + await config.afterAll() + }) + it("Check Screen access for BASIC Role", async () => { // Set up user const appUser = generateUser() diff --git a/qa-core/src/tests/internal-api/userManagement/userManagement.spec.ts b/qa-core/src/tests/internal-api/userManagement/userManagement.spec.ts index 32820b8b7f..5f0d963711 100644 --- a/qa-core/src/tests/internal-api/userManagement/userManagement.spec.ts +++ b/qa-core/src/tests/internal-api/userManagement/userManagement.spec.ts @@ -1,16 +1,18 @@ import TestConfiguration from "../../../config/internal-api/TestConfiguration" import { Application } from "@budibase/server/api/controllers/public/mapping/types" import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient" +import AccountsAPIClient from "../../../config/internal-api/TestConfiguration/accountsAPIClient" import { generateUser } from "../../../config/internal-api/fixtures/userManagement" import { User } from "@budibase/types" describe("Internal API - User Management & Permissions", () => { const api = new InternalAPIClient() - const config = new TestConfiguration(api) + const accountsAPI = new AccountsAPIClient() + const config = new TestConfiguration(api, accountsAPI) // Before each test, login as admin. Some tests will require login as a different user - beforeEach(async () => { - await config.loginAsAdmin() + beforeAll(async () => { + await config.setupAccountAndTenant() }) afterAll(async () => {