diff --git a/qa-core/.env b/qa-core/.env index 096fb4e157..be24fd2d28 100644 --- a/qa-core/.env +++ b/qa-core/.env @@ -1,6 +1,5 @@ BB_ADMIN_USER_EMAIL=qa@budibase.com BB_ADMIN_USER_PASSWORD=budibase -ENCRYPTED_TEST_PUBLIC_API_KEY=a65722f06bee5caeadc5d7ca2f543a43-d610e627344210c643bb726f COUCH_DB_URL=http://budibase:budibase@localhost:4567 COUCH_DB_USER=budibase COUCH_DB_PASSWORD=budibase diff --git a/qa-core/scripts/jestSetup.js b/qa-core/scripts/jestSetup.js index cd63258f7a..a6f8a6478c 100644 --- a/qa-core/scripts/jestSetup.js +++ b/qa-core/scripts/jestSetup.js @@ -1,11 +1,3 @@ -const env = require("../src/environment") - -env._set("BUDIBASE_SERVER_URL", "http://localhost:4100") -env._set( - "BUDIBASE_PUBLIC_API_KEY", - "a65722f06bee5caeadc5d7ca2f543a43-d610e627344210c643bb726f" -) - // mock all dates to 2020-01-01T00:00:00.000Z // use tk.reset() to use real dates in individual tests const MOCK_DATE = new Date("2020-01-01T00:00:00.000Z") diff --git a/qa-core/src/config/internal-api/TestConfiguration/InternalAPIClient.ts b/qa-core/src/config/internal-api/TestConfiguration/InternalAPIClient.ts index ef47d8a12b..1c79f47609 100644 --- a/qa-core/src/config/internal-api/TestConfiguration/InternalAPIClient.ts +++ b/qa-core/src/config/internal-api/TestConfiguration/InternalAPIClient.ts @@ -49,14 +49,16 @@ class InternalAPIClient { // @ts-ignore const response = await fetch(`https://${process.env.TENANT_ID}.${this.host}${url}`, requestOptions) - if (response.status == 404 || response.status == 500) { + if ( + response.status == 404 || + response.status == 500 || + response.status == 403 + ) { 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) + console.error("Response:", response) + const json = await response.json() + console.error("Response body:", json) + console.error("Request body:", requestOptions.body) } return response } diff --git a/qa-core/src/config/internal-api/TestConfiguration/accountsAPIClient.ts b/qa-core/src/config/internal-api/TestConfiguration/accountsAPIClient.ts index aff821a7ac..9de03b75b6 100644 --- a/qa-core/src/config/internal-api/TestConfiguration/accountsAPIClient.ts +++ b/qa-core/src/config/internal-api/TestConfiguration/accountsAPIClient.ts @@ -44,12 +44,10 @@ class AccountsAPIClient { 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) + console.error("Response:", response) + const json = await response.json() + console.error("Response body:", json) + console.error("Request body:", requestOptions.body) } return response } diff --git a/qa-core/src/config/internal-api/fixtures/accounts.ts b/qa-core/src/config/internal-api/fixtures/accounts.ts index dbeabae928..64b6d51f44 100644 --- a/qa-core/src/config/internal-api/fixtures/accounts.ts +++ b/qa-core/src/config/internal-api/fixtures/accounts.ts @@ -5,7 +5,8 @@ import { Hosting } from "@budibase/types" export const generateAccount = (): Partial => { const randomGuid = generator.guid() - let tenant: string = "a" + randomGuid + //Needs to start with a letter + let tenant: string = "tenant" + randomGuid tenant = tenant.replace(/-/g, "") return { diff --git a/qa-core/src/config/public-api/TestConfiguration/PublicAPIClient.ts b/qa-core/src/config/public-api/TestConfiguration/PublicAPIClient.ts index 3721e31da3..0dfe74bb6a 100644 --- a/qa-core/src/config/public-api/TestConfiguration/PublicAPIClient.ts +++ b/qa-core/src/config/public-api/TestConfiguration/PublicAPIClient.ts @@ -11,20 +11,32 @@ interface ApiOptions { class PublicAPIClient { host: string - apiKey: string + apiKey?: string + tenantName?: string appId?: string + cookie?: string constructor(appId?: string) { - if (!env.BUDIBASE_PUBLIC_API_KEY || !env.BUDIBASE_SERVER_URL) { + if (!env.BUDIBASE_HOST) { throw new Error( "Must set BUDIBASE_PUBLIC_API_KEY and BUDIBASE_SERVER_URL env vars" ) } - this.host = `${env.BUDIBASE_SERVER_URL}/api/public/v1` - this.apiKey = env.BUDIBASE_PUBLIC_API_KEY + this.host = `${env.BUDIBASE_HOST}/api/public/v1` + this.appId = appId } + setTenantName(tenantName: string) { + this.tenantName = tenantName + } + + setApiKey(apiKey: string) { + this.apiKey = apiKey + process.env.BUDIBASE_PUBLIC_API_KEY = apiKey + this.host = `${env.BUDIBASE_HOST}/api/public/v1` + } + apiCall = (method: APIMethod) => async (url = "", options: ApiOptions = {}) => { @@ -32,18 +44,27 @@ class PublicAPIClient { method, body: JSON.stringify(options.body), headers: { - "x-budibase-api-key": this.apiKey, + "x-budibase-api-key": this.apiKey || null, "x-budibase-app-id": this.appId, "Content-Type": "application/json", Accept: "application/json", ...options.headers, + cookie: this.cookie, + redirect: "follow", + follow: 20, }, } + // prettier-ignore // @ts-ignore - const response = await fetch(`${this.host}${url}`, requestOptions) - if (response.status !== 200) { - console.error(response) + const response = await fetch(`https://${process.env.TENANT_ID}.${this.host}${url}`, requestOptions) + + if (response.status == 500 || response.status == 403) { + console.error("Error in apiCall") + console.error("Response:", response) + const json = await response.json() + console.error("Response body:", json) + console.error("Request body:", requestOptions.body) } return response } diff --git a/qa-core/src/config/public-api/TestConfiguration/accounts.ts b/qa-core/src/config/public-api/TestConfiguration/accounts.ts new file mode 100644 index 0000000000..fdf5aedbd0 --- /dev/null +++ b/qa-core/src/config/public-api/TestConfiguration/accounts.ts @@ -0,0 +1,38 @@ +import { Response } from "node-fetch" +import { Account } from "@budibase/types" +import AccountsAPIClient from "./accountsAPIClient" +import { NewAccount } from "../fixtures/types/newAccount" + +export default class AccountsApi { + api: AccountsAPIClient + + constructor(AccountsAPIClient: AccountsAPIClient) { + this.api = AccountsAPIClient + } + + async validateEmail(email: string): Promise { + const response = await this.api.post(`/accounts/validate/email`, { + body: { email }, + }) + expect(response).toHaveStatusCode(200) + return response + } + + async validateTenantId(tenantId: string): Promise { + const response = await this.api.post(`/accounts/validate/tenantId`, { + body: { tenantId }, + }) + expect(response).toHaveStatusCode(200) + return response + } + + 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(201) + return [response, json] + } +} diff --git a/qa-core/src/config/public-api/TestConfiguration/accountsAPIClient.ts b/qa-core/src/config/public-api/TestConfiguration/accountsAPIClient.ts new file mode 100644 index 0000000000..2ea465adda --- /dev/null +++ b/qa-core/src/config/public-api/TestConfiguration/accountsAPIClient.ts @@ -0,0 +1,66 @@ +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 || + response.status == 400 + ) { + console.error("Error in apiCall") + console.error("Response:", response) + const json = await response.json() + console.error("Response body:", json) + console.error("Request body:", 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/public-api/TestConfiguration/applications.ts b/qa-core/src/config/public-api/TestConfiguration/applications.ts index ab25930544..56b0b70795 100644 --- a/qa-core/src/config/public-api/TestConfiguration/applications.ts +++ b/qa-core/src/config/public-api/TestConfiguration/applications.ts @@ -63,4 +63,15 @@ export default class AppApi { const response = await this.api.post(`/applications/${id}/unpublish`) return [response] } + + async createFirstApp() { + const body = { + name: "My first app", + url: "my-first-app", + useTemplate: false, + sampleData: true, + } + const response = await this.api.post("/applications", { body }) + expect(response).toHaveStatusCode(200) + } } diff --git a/qa-core/src/config/public-api/TestConfiguration/auth.ts b/qa-core/src/config/public-api/TestConfiguration/auth.ts new file mode 100644 index 0000000000..3eb4df2245 --- /dev/null +++ b/qa-core/src/config/public-api/TestConfiguration/auth.ts @@ -0,0 +1,48 @@ +import { Response } from "node-fetch" +import AccountsAPIClient from "./accountsAPIClient" +import { ApiKeyResponse } from "../fixtures/types/apiKeyResponse" + +export default class AuthApi { + api: AccountsAPIClient + + constructor(apiClient: AccountsAPIClient) { + this.api = apiClient + } + + async loginAsAdmin(): Promise<[Response, any]> { + const response = await this.api.post(`/auth/login`, { + body: { + username: process.env.BB_ADMIN_USER_EMAIL, + password: process.env.BB_ADMIN_USER_PASSWORD, + }, + }) + const cookie = response.headers.get("set-cookie") + this.api.cookie = cookie as any + return [response, cookie] + } + + async login(email: String, password: String): Promise<[Response, any]> { + const response = await this.api.post(`/global/auth/default/login`, { + body: { + username: email, + password: password, + }, + }) + expect(response).toHaveStatusCode(200) + const cookie = response.headers.get("set-cookie") + this.api.cookie = cookie as any + return [response, cookie] + } + + async logout(): Promise { + return this.api.post(`/global/auth/logout`) + } + + async getApiKey(): Promise { + const response = await this.api.get(`/global/self/api_key`) + const json = await response.json() + expect(response).toHaveStatusCode(200) + expect(json).toHaveProperty("apiKey") + return json + } +} diff --git a/qa-core/src/config/public-api/TestConfiguration/index.ts b/qa-core/src/config/public-api/TestConfiguration/index.ts index 36cc3022b0..e67da27883 100644 --- a/qa-core/src/config/public-api/TestConfiguration/index.ts +++ b/qa-core/src/config/public-api/TestConfiguration/index.ts @@ -3,19 +3,82 @@ import ApplicationApi from "./applications" import TableApi from "./tables" import UserApi from "./users" import RowApi from "./rows" +import AuthApi from "./auth" +import AccountsApiClient from "./accountsAPIClient" +import AccountsApi from "./accounts" +import { generateAccount } from "../fixtures/accounts" +import internalApplicationsApi from "../../internal-api/TestConfiguration/applications" + +import InternalAPIClient from "../../internal-api/TestConfiguration/InternalAPIClient" export default class TestConfiguration { applications: ApplicationApi + auth: AuthApi users: UserApi tables: TableApi rows: RowApi context: T + accounts: AccountsApi + apiClient: PublicAPIClient + accountsApiClient: AccountsApiClient + internalApiClient: InternalAPIClient + internalApplicationsApi: internalApplicationsApi - constructor(apiClient: PublicAPIClient) { + constructor( + apiClient: PublicAPIClient, + accountsApiClient: AccountsApiClient, + internalApiClient: InternalAPIClient + ) { + this.apiClient = apiClient + this.accountsApiClient = accountsApiClient + this.internalApiClient = internalApiClient + + this.auth = new AuthApi(this.internalApiClient) + this.accounts = new AccountsApi(this.accountsApiClient) this.applications = new ApplicationApi(apiClient) this.users = new UserApi(apiClient) this.tables = new TableApi(apiClient) this.rows = new RowApi(apiClient) + this.internalApplicationsApi = new internalApplicationsApi( + internalApiClient + ) + + this.context = {} + } + + async setupAccountAndTenant() { + // This step is required to create a new account and tenant for the tests, its part of + // the support for running tests in multiple environments. + const account = generateAccount() + 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.updateApiClients(account.tenantName) + await this.auth.login(account.email, account.password) + const body = { + name: "My first app", + url: "my-first-app", + useTemplate: false, + sampleData: true, + } + await this.internalApplicationsApi.create(body) + } + + // After the account and tenant have been created, we need to get and set the API key for the test + async setApiKey() { + const apiKeyResponse = await this.auth.getApiKey() + this.apiClient.setApiKey(apiKeyResponse.apiKey) + } + async updateApiClients(tenantName: string) { + this.apiClient.setTenantName(tenantName) + this.applications = new ApplicationApi(this.apiClient) + this.rows = new RowApi(this.apiClient) + this.internalApiClient.setTenantName(tenantName) + this.internalApplicationsApi = new internalApplicationsApi( + this.internalApiClient + ) + this.auth = new AuthApi(this.internalApiClient) this.context = {} } diff --git a/qa-core/src/config/public-api/fixtures/accounts.ts b/qa-core/src/config/public-api/fixtures/accounts.ts new file mode 100644 index 0000000000..64b6d51f44 --- /dev/null +++ b/qa-core/src/config/public-api/fixtures/accounts.ts @@ -0,0 +1,22 @@ +import { NewAccount } from "./types/newAccount" + +import generator from "../../generator" +import { Hosting } from "@budibase/types" + +export const generateAccount = (): Partial => { + const randomGuid = generator.guid() + //Needs to start with a letter + let tenant: string = "tenant" + randomGuid + tenant = tenant.replace(/-/g, "") + + return { + email: `qa+${randomGuid}@budibase.com`, + hosting: Hosting.CLOUD, + name: `qa+${randomGuid}@budibase.com`, + password: `${randomGuid}`, + profession: "software_engineer", + size: "10+", + tenantId: `${tenant}`, + tenantName: `${tenant}`, + } +} diff --git a/qa-core/src/config/public-api/fixtures/types/apiKeyResponse.ts b/qa-core/src/config/public-api/fixtures/types/apiKeyResponse.ts new file mode 100644 index 0000000000..4a62d60796 --- /dev/null +++ b/qa-core/src/config/public-api/fixtures/types/apiKeyResponse.ts @@ -0,0 +1,6 @@ +export interface ApiKeyResponse { + apiKey: string + createdAt: string + updatedAt: string + userId: string +} diff --git a/qa-core/src/config/public-api/fixtures/types/newAccount.ts b/qa-core/src/config/public-api/fixtures/types/newAccount.ts new file mode 100644 index 0000000000..e7ad88e697 --- /dev/null +++ b/qa-core/src/config/public-api/fixtures/types/newAccount.ts @@ -0,0 +1,5 @@ +import { Account } from "@budibase/types" + +export interface NewAccount extends Account { + password: string +} diff --git a/qa-core/src/tests/public-api/applications/applications.spec.ts b/qa-core/src/tests/public-api/applications/applications.spec.ts index cf85e6daf2..a5c0ed3691 100644 --- a/qa-core/src/tests/public-api/applications/applications.spec.ts +++ b/qa-core/src/tests/public-api/applications/applications.spec.ts @@ -1,15 +1,25 @@ import TestConfiguration from "../../../config/public-api/TestConfiguration" import PublicAPIClient from "../../../config/public-api/TestConfiguration/PublicAPIClient" +import AccountsAPIClient from "../../../config/public-api/TestConfiguration/accountsAPIClient" import generateApp from "../../../config/public-api/fixtures/applications" import { Application } from "@budibase/server/api/controllers/public/mapping/types" import { db as dbCore } from "@budibase/backend-core" +import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient" describe("Public API - /applications endpoints", () => { const api = new PublicAPIClient() - const config = new TestConfiguration(api) + const accountsAPI = new AccountsAPIClient() + const internalAPI = new InternalAPIClient() + const config = new TestConfiguration( + api, + accountsAPI, + internalAPI + ) beforeAll(async () => { - await config.beforeAll() + await config.setupAccountAndTenant() + await config.setApiKey() + const [response, app] = await config.applications.seed() config.context = app }) diff --git a/qa-core/src/tests/public-api/tables/rows.spec.ts b/qa-core/src/tests/public-api/tables/rows.spec.ts index 89149159ab..d21b61e41a 100644 --- a/qa-core/src/tests/public-api/tables/rows.spec.ts +++ b/qa-core/src/tests/public-api/tables/rows.spec.ts @@ -2,14 +2,19 @@ import { Row } from "@budibase/server/api/controllers/public/mapping/types" import { generateRow } from "../../../config/public-api/fixtures/tables" import TestConfiguration from "../../../config/public-api/TestConfiguration" import PublicAPIClient from "../../../config/public-api/TestConfiguration/PublicAPIClient" +import AccountsAPIClient from "../../../config/public-api/TestConfiguration/accountsAPIClient" +import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient" describe("Public API - /rows endpoints", () => { - let api = new PublicAPIClient() - - const config = new TestConfiguration(api) + const api = new PublicAPIClient() + const accountsAPI = new AccountsAPIClient() + const internalAPI = new InternalAPIClient() + const config = new TestConfiguration(api, accountsAPI, internalAPI) beforeAll(async () => { - await config.beforeAll() + await config.setupAccountAndTenant() + await config.setApiKey() + const [aResp, app] = await config.applications.seed() config.tables.api.appId = app._id diff --git a/qa-core/src/tests/public-api/tables/tables.spec.ts b/qa-core/src/tests/public-api/tables/tables.spec.ts index de1ce142ce..fc506d7bb6 100644 --- a/qa-core/src/tests/public-api/tables/tables.spec.ts +++ b/qa-core/src/tests/public-api/tables/tables.spec.ts @@ -2,13 +2,19 @@ import { Table } from "@budibase/server/api/controllers/public/mapping/types" import { generateTable } from "../../../config/public-api/fixtures/tables" import TestConfiguration from "../../../config/public-api/TestConfiguration" import PublicAPIClient from "../../../config/public-api/TestConfiguration/PublicAPIClient" +import AccountsAPIClient from "../../../config/public-api/TestConfiguration/accountsAPIClient" +import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient" describe("Public API - /tables endpoints", () => { - let api = new PublicAPIClient() - const config = new TestConfiguration(api) + const api = new PublicAPIClient() + const accountsAPI = new AccountsAPIClient() + const internalAPI = new InternalAPIClient() + const config = new TestConfiguration
(api, accountsAPI, internalAPI) beforeAll(async () => { - await config.beforeAll() + await config.setupAccountAndTenant() + await config.setApiKey() + const [appResp, app] = await config.applications.seed() config.tables.api.appId = app._id diff --git a/qa-core/src/tests/public-api/users/users.spec.ts b/qa-core/src/tests/public-api/users/users.spec.ts index 5e68c77c50..597a8ff2dd 100644 --- a/qa-core/src/tests/public-api/users/users.spec.ts +++ b/qa-core/src/tests/public-api/users/users.spec.ts @@ -2,13 +2,18 @@ import TestConfiguration from "../../../config/public-api/TestConfiguration" import PublicAPIClient from "../../../config/public-api/TestConfiguration/PublicAPIClient" import generateUser from "../../../config/public-api/fixtures/users" import { User } from "@budibase/server/api/controllers/public/mapping/types" +import AccountsAPIClient from "../../../config/public-api/TestConfiguration/accountsAPIClient" +import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient" describe("Public API - /users endpoints", () => { const api = new PublicAPIClient() - const config = new TestConfiguration(api) + const accountsAPI = new AccountsAPIClient() + const internalAPI = new InternalAPIClient() + const config = new TestConfiguration(api, accountsAPI, internalAPI) beforeAll(async () => { - await config.beforeAll() + await config.setupAccountAndTenant() + await config.setApiKey() const [_, user] = await config.users.seed() config.context = user })