Restructure account tests to test signup and deletion as a single flow

This commit is contained in:
Rory Powell 2023-07-18 21:15:13 +01:00
parent 944b6e0baa
commit 4dc558d3e9
11 changed files with 133 additions and 255 deletions

View File

@ -1,6 +1,8 @@
import { Response } from "node-fetch"
import env from "../../environment"
import fetch, { HeadersInit } from "node-fetch"
import { State } from "../../types"
import { Header } from "@budibase/backend-core"
type APIMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"
@ -28,7 +30,7 @@ export default class AccountInternalAPIClient {
apiCall =
(method: APIMethod) =>
async (url = "", options: ApiOptions = {}) => {
async (url = "", options: ApiOptions = {}): Promise<[Response, any]> => {
const requestOptions = {
method,
body: JSON.stringify(options.body),
@ -46,7 +48,7 @@ export default class AccountInternalAPIClient {
if (options.internal) {
requestOptions.headers = {
...requestOptions.headers,
...{ "x-budibase-api-key": env.ACCOUNT_PORTAL_API_KEY },
...{ [Header.API_KEY] : env.ACCOUNT_PORTAL_API_KEY },
}
}

View File

@ -1,6 +1,7 @@
import { generator } from "../../shared"
import { Hosting, CreateAccountRequest } from "@budibase/types"
// TODO: Refactor me to central location
export const generateAccount = (): CreateAccountRequest => {
const uuid = generator.guid()

View File

@ -0,0 +1,29 @@
import TestConfiguration from "../../config/TestConfiguration"
import * as fixtures from "../../fixtures"
import { generator } from "../../../shared"
describe("Account Internal Operations", () => {
const config = new TestConfiguration()
beforeAll(async () => {
await config.beforeAll()
})
afterAll(async () => {
await config.afterAll()
})
it("performs account deletion", async () => {
// Deleting by unknown id doesn't work
const accountId = generator.string()
await config.api.accounts.delete(accountId, { status: 404 })
// Create new account
const [_, account] = await config.api.accounts.create({
...fixtures.accounts.generateAccount(),
})
// New account can be deleted
await config.api.accounts.delete(account.accountId)
})
})

View File

@ -0,0 +1,80 @@
import TestConfiguration from "../../config/TestConfiguration"
import * as fixtures from "../../fixtures"
import { generator } from "../../../shared"
describe("accounts", () => {
const config = new TestConfiguration()
beforeAll(async () => {
await config.beforeAll()
})
afterAll(async () => {
await config.afterAll()
})
it("performs signup and deletion flow", async () => {
await config.doInNewState(async () => {
const createAccountRequest = fixtures.accounts.generateAccount()
const email = createAccountRequest.email
const tenantId = createAccountRequest.tenantId
// Validation - email and tenant id allowed
await config.api.accounts.validateEmail(email)
await config.api.accounts.validateTenantId(tenantId)
// Create unverified account
await config.api.accounts.create(createAccountRequest)
// Validation - email and tenant id no longer valid
await config.api.accounts.validateEmail(email, { status: 400 })
await config.api.accounts.validateTenantId(tenantId, { status: 400 })
// Attempt to log in using unverified account
await config.loginAsAccount(createAccountRequest, { status: 400 })
// Re-send verification email to get access to code
const [_, code] = await config.accountsApi.accounts.sendVerificationEmail(email)
// Send the verification request
await config.accountsApi.accounts.verifyAccount(code!)
// Can now login to the account
await config.loginAsAccount(createAccountRequest)
// Delete account
await config.api.accounts.deleteCurrentAccount()
// Can't login
await config.loginAsAccount(createAccountRequest, { status: 403 })
})
})
describe("searching accounts", () => {
it("searches by tenant id", async () => {
const tenantId = generator.string()
// empty result
const [emptyRes, emptyBody] = await config.api.accounts.search(tenantId, "tenantId")
expect(emptyBody.length).toBe(0)
// hit result
const [hitRes, hitBody] = await config.api.accounts.search(config.state.tenantId!, "tenantId")
expect(hitBody.length).toBe(1)
expect(hitBody[0].tenantId).toBe(config.state.tenantId)
})
it("searches by email", async () => {
const email = generator.email()
// empty result
const [emptyRes, emptyBody] = await config.api.accounts.search(email, "email")
expect(emptyBody.length).toBe(0)
// hit result
const [hitRes, hitBody] = await config.api.accounts.search(config.state.email!, "email")
expect(hitBody.length).toBe(1)
expect(hitBody[0].email).toBe(config.state.email)
})
})
})

View File

@ -1,23 +0,0 @@
import TestConfiguration from "../../config/TestConfiguration"
import * as fixtures from "../../fixtures"
describe("Account API - Create Account", () => {
const config = new TestConfiguration()
beforeAll(async () => {
await config.beforeAll()
})
afterAll(async () => {
await config.afterAll()
})
describe("POST /api/accounts/", () => {
it("Returns 201", async () => {
const [res, account] = await config.api.accounts.create({
...fixtures.accounts.generateAccount()
})
expect(res.status).toBe(201)
})
})
})

View File

@ -1,52 +0,0 @@
import TestConfiguration from "../../config/TestConfiguration"
import * as fixtures from "../../fixtures"
import { generator } from "../../../shared"
describe("Account API - Delete Account", () => {
const config = new TestConfiguration()
beforeAll(async () => {
await config.beforeAll()
})
afterAll(async () => {
await config.afterAll()
})
describe("DEL /api/accounts", () => {
it("Returns 204", async () => {
await config.doInNewState(async () => {
// Create account
const createAccountRequest = fixtures.accounts.generateAccount()
await config.api.accounts.create(createAccountRequest)
// Login - Get cookie
await config.login(
createAccountRequest.email,
createAccountRequest.password,
createAccountRequest.tenantId
)
// Delete account
const res = await config.api.accounts.deleteCurrentAccount()
expect(res.status).toBe(204)
})
})
})
describe("DEL /api/accounts/{accountId}", () => {
it("Returns 204", async () => {
const [response, account] = await config.api.accounts.create({
...fixtures.accounts.generateAccount()
})
// Delete account by ID
const res = await config.api.accounts.delete(account.accountId)
expect(res.status).toBe(204)
})
it("returns 404 - Account not found", async () => {
const accountId = generator.string()
await config.api.accounts.delete(accountId, {status:404})
})
})
})

View File

@ -1,70 +0,0 @@
import TestConfiguration from "../../config/TestConfiguration"
import { generator } from "../../../shared"
describe("Account API - Search for Account", () => {
const config = new TestConfiguration()
beforeAll(async () => {
await config.beforeAll()
})
afterAll(async () => {
await config.afterAll()
})
describe("POST /api/accounts/search", () => {
describe("by tenant", () => {
it("returns 200 + empty", async () => {
const tenantId = generator.string()
const [res, body] =
await config.api.accounts.search(tenantId, "tenantId")
expect(res.status).toBe(200)
expect(body.length).toBe(0)
})
it("returns 200 + found", async () => {
const [res, body] =
await config.api.accounts.search(config.state.tenantId!, "tenantId")
expect(res.status).toBe(200)
expect(body.length).toBe(1)
expect(body[0].tenantId).toBe(config.state.tenantId)
})
it("returns 400 + error: Invalid body - tenantId is not allowed to be empty", async () => {
const [res, body] =
await config.api.accounts.search("", "tenantId")
expect(body).toEqual({
message: "Invalid body - \"tenantId\" is not allowed to be empty",
status: 400
})
})
})
describe("by email", () => {
it("returns 200 + empty", async () => {
const email = generator.email()
const [res, body] =
await config.api.accounts.search(email, "email")
expect(res.status).toBe(200)
expect(body.length).toBe(0)
})
it("returns 200 + found", async () => {
const [res, body] =
await config.api.accounts.search(config.state.email!, "email")
expect(res.status).toBe(200)
expect(body.length).toBe(1)
expect(body[0].email).toBe(config.state.email)
})
it("returns 400 + error: Invalid body - email is not allowed to be empty", async () => {
const [res, body] =
await config.api.accounts.search("", "email")
expect(body).toEqual({
message: "Invalid body - \"email\" is not allowed to be empty",
status: 400
})
})
})
})
})

View File

@ -1,46 +0,0 @@
import TestConfiguration from "../../config/TestConfiguration"
import * as fixtures from "../../fixtures"
import { generator } from "../../../shared"
describe("Account API - Validate Account", () => {
const config = new TestConfiguration()
beforeAll(async () => {
await config.beforeAll()
})
afterAll(async () => {
await config.afterAll()
})
describe("POST /api/accounts/validate/email", () => {
it("Returns 200", async () => {
const email = generator.email()
const res = await config.api.accounts.validateEmail(email)
expect(res.status).toBe(200)
})
it("returns 400", async () => {
const [response, account] = await config.api.accounts.create({
...fixtures.accounts.generateAccount()
})
const res = await config.api.accounts.validateEmail(account.email)
expect(res.status).toBe(400)
})
})
describe("POST /api/accounts/validate/tenantId", () => {
it("Returns 200", async () => {
const res = await config.api.accounts.validateTenantId("randomtenant")
expect(res.status).toBe(200)
})
it("Returns 400", async () => {
const [response, account] = await config.api.accounts.create({
...fixtures.accounts.generateAccount()
})
const res = await config.api.accounts.validateTenantId(account.tenantId)
expect(res.status).toBe(400)
})
})
})

View File

@ -1,56 +0,0 @@
import TestConfiguration from "../../config/TestConfiguration"
import * as fixtures from "../../fixtures"
describe("Account API - Verify Account", () => {
const config = new TestConfiguration()
beforeAll(async () => {
await config.beforeAll()
})
afterAll(async () => {
await config.afterAll()
})
describe("POST /api/accounts/verify", () => {
it("returns 200", async () => {
// Create unverified account
const createAccountRequest = fixtures.accounts.generateAccount()
const [res, acc] = await config.api.accounts.create(
createAccountRequest,
{ doExpect: true, autoVerify: false })
// Attempt to log in using unverified account
const [loginResponse, cookie] = await config.accountsApi.auth.login(
createAccountRequest.email,
createAccountRequest.password,
)
// await config.login(
// createAccountRequest.email,
// createAccountRequest.password,
// createAccountRequest.tenantId,
// )
// Expect response - cannot login via unverified account
// Verify account via code
// await config.api.accounts.verifyAccount()
// Expect response - login successful
})
})
describe("POST /api/accounts/verify/send", () => {
it("Send account verification email ", async () => {
// Create account
await config.api.accounts.create({
...fixtures.accounts.generateAccount()
})
// Verify account via email
//await config.api.accounts.verifyAccountSendEmail()
})
})
})

View File

@ -10,8 +10,11 @@ const API_OPTS: APIRequestOpts = { doExpect: false }
async function deleteAccount() {
// @ts-ignore
const accountID = global.qa.accountId
// can't run 'expect' blocks in teardown
await accountsApi.accounts.delete(accountID)
const [response] = await accountsApi.accounts.delete(accountID, { doExpect: false })
if (response.status !== 204) {
throw new Error(`status: ${response.status} not equal to expected: 201`)
}
}
async function teardown() {

View File

@ -1,7 +1,8 @@
import { BudibaseInternalAPI } from "../internal-api"
import { AccountInternalAPI } from "../account-api"
import { CreateAppRequest, State } from "../types"
import { APIRequestOpts, CreateAppRequest, State } from "../types"
import * as fixtures from "../internal-api/fixtures"
import { CreateAccountRequest } from "@budibase/types"
export default class BudibaseTestConfiguration {
// apis
@ -42,12 +43,12 @@ export default class BudibaseTestConfiguration {
// AUTH
async doInNewState(task: any) {
async doInNewState(task: () => Promise<any>) {
return this.doWithState(task, {})
}
async doWithState(task: any, state: State) {
const original = this.state
async doWithState(task: () => Promise<any>, state: State) {
const original = { ...this.state }
// override the state
this.state.apiKey = state.apiKey
@ -68,6 +69,15 @@ export default class BudibaseTestConfiguration {
this.state.email = original.email
}
async loginAsAccount(account: CreateAccountRequest, opts: APIRequestOpts = {}) {
const [_, cookie] = await this.accountsApi.auth.login(
account.email,
account.password,
opts
)
this.state.cookie = cookie
}
async login(email: string, password: string, tenantId?: string) {
if (!tenantId && this.state.tenantId) {
tenantId = this.state.tenantId