Run integration suite in CI again / auto detect tenancy / refactors (#10209)

* qa-core-ci-fixes

* global setup and teardown wip

* Updates to logs and setup

* Remove date and console mocking

* Update CI to spin up minimal dev env

* Update readme

* Fix scopeBackend.sh

* Ensure docker services are initialised before starting worker

* Lint

* Fix admin user being created on startup (#10219)

* use regular bootstrap and build

* Lint

* Temp: re-use global setup to get around app limit in QA
This commit is contained in:
Rory Powell 2023-04-05 15:33:56 +01:00 committed by GitHub
parent 18ccf134b9
commit 77ffb8d86d
140 changed files with 2835 additions and 2993 deletions

View File

@ -77,28 +77,21 @@ jobs:
- run: yarn bootstrap
- run: yarn test:pro
# integration-test:
# runs-on: ubuntu-latest
# services:
# couchdb:
# image: ibmcom/couchdb3
# env:
# COUCHDB_PASSWORD: budibase
# COUCHDB_USER: budibase
# ports:
# - 4567:5984
# steps:
# - uses: actions/checkout@v2
# - name: Use Node.js 14.x
# uses: actions/setup-node@v1
# with:
# node-version: 14.x
# - name: Install Pro
# run: yarn install:pro $BRANCH $BASE_BRANCH
# - run: yarn
# - run: yarn bootstrap
# - run: yarn build
# - run: |
# cd qa-core
# yarn
# yarn api:test:ci
integration-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 14.x
- name: Install Pro
run: yarn install:pro $BRANCH $BASE_BRANCH
- run: yarn && yarn bootstrap && yarn build
- run: |
cd qa-core
yarn setup
yarn test:ci
env:
BB_ADMIN_USER_EMAIL: admin
BB_ADMIN_USER_PASSWORD: admin

View File

@ -23,10 +23,12 @@
},
"scripts": {
"setup": "node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev",
"bootstrap": "lerna bootstrap && lerna link && ./scripts/link-dependencies.sh",
"bootstrap": "lerna bootstrap",
"postbootstrap": "lerna link && ./scripts/link-dependencies.sh",
"build": "lerna run --stream build",
"build:dev": "lerna run --stream prebuild && tsc --build --watch --preserveWatchOutput",
"build:backend": "lerna run --stream build --ignore @budibase/client --ignore @budibase/bbui --ignore @budibase/builder --ignore @budibase/cli",
"backend:bootstrap": "./scripts/scopeBackend.sh 'lerna bootstrap' && yarn run postbootstrap",
"backend:build": "./scripts/scopeBackend.sh 'lerna run --stream build'",
"build:sdk": "lerna run --stream build:sdk",
"deps:circular": "madge packages/server/dist/index.js packages/worker/src/index.ts packages/backend-core/dist/src/index.js packages/cli/src/index.js --circular",
"release": "lerna publish ${RELEASE_VERSION_TYPE:-patch} --yes --force-publish && yarn release:pro",
@ -44,6 +46,7 @@
"dev": "yarn run kill-all && lerna link && lerna run --stream --parallel dev:builder --concurrency 1 --stream",
"dev:noserver": "yarn run kill-builder && lerna link && lerna run --stream dev:stack:up && lerna run --stream --parallel dev:builder --concurrency 1 --ignore @budibase/backend-core --ignore @budibase/server --ignore @budibase/worker",
"dev:server": "yarn run kill-server && lerna run --stream --parallel dev:builder --concurrency 1 --scope @budibase/backend-core --scope @budibase/worker --scope @budibase/server",
"dev:built": "cd packages/server && yarn dev:stack:up && cd ../../ && lerna run --stream --parallel dev:built",
"test": "lerna run --stream test --stream",
"test:pro": "bash scripts/pro/test.sh",
"lint:eslint": "eslint packages && eslint qa-core",

View File

@ -1,3 +1,6 @@
export * as correlation from "./correlation/correlation"
export { default as logger } from "./pino/logger"
export * from "./alerts"
// turn off or on context logging i.e. tenantId, appId etc
export let LOG_CONTEXT = true

View File

@ -3,6 +3,7 @@ import pino, { LoggerOptions } from "pino"
import * as context from "../../context"
import * as correlation from "../correlation"
import { IdentityType } from "@budibase/types"
import { LOG_CONTEXT } from "../index"
// LOGGER
@ -77,14 +78,22 @@ function getLogParams(args: any[]): [MergingObject, string] {
const identity = getIdentity()
let contextObject = {}
if (LOG_CONTEXT) {
contextObject = {
tenantId: getTenantId(),
appId: getAppId(),
identityId: identity?._id,
identityType: identity?.type,
correlationId: correlation.getId(),
}
}
const mergingObject = {
objects: objects.length ? objects : undefined,
tenantId: getTenantId(),
appId: getAppId(),
identityId: identity?._id,
identityType: identity?.type,
correlationId: correlation.getId(),
err: error,
...contextObject,
}
return [mergingObject, message]

View File

@ -1,10 +0,0 @@
{
"reporterEnabled": "mochawesome",
"mochawesomeReporterOptions": {
"reportDir": "cypress/reports",
"quiet": true,
"overwrite": false,
"html": false,
"json": true
}
}

View File

@ -1,43 +0,0 @@
const testConfig = require("./testConfig.json")
// normal development system
const SERVER_PORT = testConfig.env.PORT
const WORKER_PORT = testConfig.env.WORKER_PORT
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = "cypress"
}
process.env.ENABLE_ANALYTICS = "0"
process.env.JWT_SECRET = testConfig.env.JWT_SECRET
process.env.SELF_HOSTED = 1
process.env.WORKER_URL = `http://localhost:${WORKER_PORT}/`
process.env.APPS_URL = `http://localhost:${SERVER_PORT}/`
process.env.MINIO_URL = `http://localhost:4004`
process.env.MINIO_ACCESS_KEY = "budibase"
process.env.MINIO_SECRET_KEY = "budibase"
process.env.COUCH_DB_USER = "budibase"
process.env.COUCH_DB_PASSWORD = "budibase"
process.env.INTERNAL_API_KEY = "budibase"
process.env.ALLOW_DEV_AUTOMATIONS = 1
process.env.MOCK_REDIS = 1
// Stop info logs polluting test outputs
process.env.LOG_LEVEL = "error"
exports.run = (serverLoc = "../server/dist", workerLoc = "../worker/dist") => {
// require("dotenv").config({ path: resolve(dir, ".env") })
// don't make this a variable or top level require
// it will cause environment module to be loaded prematurely
// override the port with the worker port temporarily
process.env.PORT = WORKER_PORT
require(workerLoc)
// override the port with the server port
process.env.PORT = SERVER_PORT
require(serverLoc)
}
if (require.main === module) {
exports.run()
}

View File

@ -1,13 +0,0 @@
{
"baseUrl": "http://localhost:4100",
"projectId": "bmbemn",
"reporterOptions": {
"configFile": "reporterConfig.json"
},
"env": {
"PORT": "4100",
"WORKER_PORT": "4200",
"JWT_SECRET": "test",
"HOST_IP": ""
}
}

View File

@ -1,4 +0,0 @@
// @ts-ignore
import { run } from "../setup"
run("../server/src/index", "../worker/src/index")

View File

@ -26,6 +26,7 @@
"dev:stack:down": "node scripts/dev/manage.js down",
"dev:stack:nuke": "node scripts/dev/manage.js nuke",
"dev:builder": "yarn run dev:stack:up && nodemon",
"dev:built": "yarn run dev:stack:up && yarn run run:docker",
"specs": "ts-node specs/generate.ts && openapi-typescript specs/openapi.yaml --output src/definitions/openapi.ts",
"initialise": "node scripts/initialise.js",
"env:multi:enable": "node scripts/multiTenancy.js enable",

View File

@ -45,7 +45,8 @@ async function init() {
BB_ADMIN_USER_PASSWORD: "",
PLUGINS_DIR: "",
TENANT_FEATURE_FLAGS: "*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR",
HTTP_LOGGING: 0,
HTTP_MIGRATIONS: "0",
HTTP_LOGGING: "0",
}
let envFile = ""
Object.keys(envFileJson).forEach(key => {

View File

@ -1,35 +0,0 @@
/******************************************************
* This script just makes it easy to re-create *
* a cypress like environment for testing the backend *
******************************************************/
import path from "path"
const tmpdir = path.join(require("os").tmpdir(), ".budibase")
const SERVER_PORT = "4100"
const WORKER_PORT = "4200"
// @ts-ignore
process.env.NODE_ENV = "cypress"
process.env.ENABLE_ANALYTICS = "0"
process.env.JWT_SECRET = "budibase"
process.env.COUCH_URL = `leveldb://${tmpdir}/.data/`
process.env.SELF_HOSTED = "1"
process.env.WORKER_URL = `http://localhost:${WORKER_PORT}/`
process.env.MINIO_URL = `http://localhost:4004`
process.env.MINIO_ACCESS_KEY = "budibase"
process.env.MINIO_SECRET_KEY = "budibase"
process.env.COUCH_DB_USER = "budibase"
process.env.COUCH_DB_PASSWORD = "budibase"
process.env.INTERNAL_API_KEY = "budibase"
process.env.ALLOW_DEV_AUTOMATIONS = "1"
// don't make this a variable or top level require
// it will cause environment module to be loaded prematurely
// override the port with the worker port temporarily
process.env.PORT = WORKER_PORT
const worker = require("../../worker/src/index")
// override the port with the server port
process.env.PORT = SERVER_PORT
const server = require("../src/app")

View File

@ -45,8 +45,10 @@ async function initPro() {
}
function shutdown(server?: any) {
server.close()
server.destroy()
if (server) {
server.close()
server.destroy()
}
}
export async function startup(app?: any, server?: any) {
@ -69,39 +71,7 @@ export async function startup(app?: any, server?: any) {
await migrations.migrate()
} catch (e) {
logging.logAlert("Error performing migrations. Exiting.", e)
shutdown()
}
}
// check and create admin user if required
if (
env.SELF_HOSTED &&
!env.MULTI_TENANCY &&
env.BB_ADMIN_USER_EMAIL &&
env.BB_ADMIN_USER_PASSWORD
) {
const checklist = await getChecklist()
if (!checklist?.adminUser?.checked) {
try {
const tenantId = tenancy.getTenantId()
const user = await createAdminUser(
env.BB_ADMIN_USER_EMAIL,
env.BB_ADMIN_USER_PASSWORD,
tenantId
)
// Need to set up an API key for automated integration tests
if (env.isTest()) {
await generateApiKey(user._id)
}
console.log(
"Admin account automatically created for",
env.BB_ADMIN_USER_EMAIL
)
} catch (e) {
logging.logAlert("Error creating initial admin user. Exiting.", e)
shutdown()
}
shutdown(server)
}
}
@ -130,4 +100,38 @@ export async function startup(app?: any, server?: any) {
// bring routes online as final step once everything ready
await initRoutes(app)
}
// check and create admin user if required
// this must be run after the api has been initialised due to
// the app user sync
if (
env.SELF_HOSTED &&
!env.MULTI_TENANCY &&
env.BB_ADMIN_USER_EMAIL &&
env.BB_ADMIN_USER_PASSWORD
) {
const checklist = await getChecklist()
if (!checklist?.adminUser?.checked) {
try {
const tenantId = tenancy.getTenantId()
const user = await createAdminUser(
env.BB_ADMIN_USER_EMAIL,
env.BB_ADMIN_USER_PASSWORD,
tenantId
)
// Need to set up an API key for automated integration tests
if (env.isTest()) {
await generateApiKey(user._id)
}
console.log(
"Admin account automatically created for",
env.BB_ADMIN_USER_EMAIL
)
} catch (e) {
logging.logAlert("Error creating initial admin user. Exiting.", e)
shutdown(server)
}
}
}
}

View File

@ -0,0 +1,13 @@
import { Hosting } from "../../sdk"
export interface CreateAccountRequest {
email: string
tenantId: string
hosting: Hosting
size: string
profession: string
// optional fields
tenantName?: string
name?: string
password: string
}

View File

@ -1,3 +1,4 @@
export * from "./accounts"
export * from "./user"
export * from "./license"
export * from "./status"

View File

@ -3,6 +3,7 @@ export * from "./auth"
export * from "./user"
export * from "./errors"
export * from "./schedule"
export * from "./system"
export * from "./app"
export * from "./global"
export * from "./pagination"

View File

@ -0,0 +1,8 @@
export interface GetEnvironmentResponse {
multiTenancy: boolean
cloud: boolean
accountPortalUrl: string
baseUrl: string
disableAccountPortal: boolean
isDev: boolean
}

View File

@ -0,0 +1 @@
export * from "./environment"

View File

@ -1,6 +1,7 @@
import Nano from "@budibase/nano"
import { AllDocsResponse, AnyDocument, Document } from "../"
import { Writable } from "stream"
import PouchDB from "pouchdb"
export enum SearchIndex {
ROWS = "rows",

View File

@ -22,6 +22,7 @@
"build:docker": "docker build . -t worker-service --label version=$BUDIBASE_RELEASE_VERSION",
"dev:stack:init": "node ./scripts/dev/manage.js init",
"dev:builder": "npm run dev:stack:init && nodemon",
"dev:built": "yarn run dev:stack:init && yarn run run:docker",
"test": "bash scripts/test.sh",
"test:watch": "jest --watch",
"env:multi:enable": "node scripts/multiTenancy.js enable",

View File

@ -1,8 +0,0 @@
BB_ADMIN_USER_EMAIL=qa@budibase.com
BB_ADMIN_USER_PASSWORD=budibase
COUCH_DB_URL=http://budibase:budibase@localhost:4567
COUCH_DB_USER=budibase
COUCH_DB_PASSWORD=budibase
JWT_SECRET=test
BUDIBASE_HOST=http://localhost:10000
BUDIBASE_ACCOUNTS_URL=http://localhost:10001

1
qa-core/.gitignore vendored
View File

@ -2,5 +2,4 @@ node_modules/
.env
watchtower-hook.json
dist/
.testReport.json
testResults.json

View File

@ -6,13 +6,11 @@ The QA Core API tests are a jest suite that run directly against the budibase ba
You can run the whole test suite with one command, that spins up the budibase server and runs the jest tests:
`yarn api:test`
`yarn test:ci`
## Setup Server Only
## Setup Server
You can also just stand up the budibase server alone.
`yarn api:server:setup`
You can run the local development stack by following the instructions on the main readme.
## Run Tests
@ -24,11 +22,7 @@ for watch mode, where the tests will run on every change:
`yarn test:watch`
To run tests locally against a cloud service you can use the command:
`yarn run api:test:local`
To run tests locally against a cloud service you can update the configuration inside the `.env` file and run:
To run the tests in CI, it assumes the correct environment variables are set, and the server is already running. Use the command:
`yarn run api:test:ci`
`yarn test`
To run the nightly tests against the QA environment, use the command:
`yarn run api:test:nightly`

View File

@ -1,12 +0,0 @@
version: "3.8"
services:
qa-core-couchdb:
# platform: linux/amd64
container_name: budi-couchdb-qa
restart: on-failure
image: ibmcom/couchdb3
environment:
- COUCHDB_PASSWORD=${COUCH_DB_PASSWORD}
- COUCHDB_USER=${COUCH_DB_USER}
ports:
- "4567:5984"

18
qa-core/jest.config.ts Normal file
View File

@ -0,0 +1,18 @@
import { Config } from "@jest/types"
const config: Config.InitialOptions = {
preset: "ts-jest",
setupFiles: ["./src/jest/jestSetup.ts"],
setupFilesAfterEnv: ["./src/jest/jest.extends.ts"],
testEnvironment: "node",
globalSetup: "./src/jest/globalSetup.ts",
globalTeardown: "./src/jest/globalTeardown.ts",
moduleNameMapper: {
"@budibase/types": "<rootDir>/../packages/types/src",
"@budibase/server": "<rootDir>/../packages/server/src",
"@budibase/backend-core": "<rootDir>/../packages/backend-core/src",
"@budibase/backend-core/(.*)": "<rootDir>/../packages/backend-core/$1",
},
}
export default config

View File

@ -9,46 +9,26 @@
"url": "https://github.com/Budibase/budibase.git"
},
"scripts": {
"test": "env-cmd jest --runInBand",
"test:watch": "env-cmd jest --watch",
"test:debug": "DEBUG=1 jest",
"setup": "yarn && node scripts/createEnv.js",
"test": "jest --runInBand --json --outputFile=testResults.json",
"test:watch": "yarn run test --watch",
"test:debug": "DEBUG=1 yarn run test",
"test:notify": "node scripts/testResultsWebhook",
"test:ci": "jest --runInBand --json --outputFile=testResults.json --testPathIgnorePatterns=\\\"\\/dataSources\\/\\\"",
"docker:up": "docker-compose up -d",
"docker:down": "docker-compose down",
"api:server:setup": "npm run docker:up && env-cmd ts-node ../packages/builder/ts/setup.ts",
"api:server:setup:ci": "env-cmd node ../packages/builder/setup.js",
"api:test": "jest --runInBand --json --outputFile=testResults.json",
"api:test:ci": "start-server-and-test api:server:setup:ci http://localhost:4100/builder test",
"api:test:local": "start-server-and-test api:server:setup http://localhost:4100/builder test"
},
"jest": {
"preset": "ts-jest",
"testEnvironment": "node",
"moduleNameMapper": {
"@budibase/types": "<rootDir>/../packages/types/src",
"@budibase/server": "<rootDir>/../packages/server/src",
"@budibase/backend-core": "<rootDir>/../packages/backend-core/src",
"@budibase/backend-core/(.*)": "<rootDir>/../packages/backend-core/$1"
},
"setupFiles": [
"./scripts/jestSetup.js"
],
"setupFilesAfterEnv": [
"./src/jest.extends.ts"
]
"test:smoke": "yarn run test --testPathIgnorePatterns=\\\"\\/dataSources\\/\\\"",
"test:ci": "start-server-and-test dev:built http://localhost:4001/health test:smoke",
"dev:built": "cd ../ && yarn dev:built"
},
"devDependencies": {
"@budibase/types": "^2.3.17",
"@types/jest": "29.0.0",
"@types/node-fetch": "2.6.2",
"chance": "1.1.8",
"env-cmd": "^10.1.0",
"jest": "28.1.1",
"dotenv": "16.0.1",
"jest": "29.0.0",
"prettier": "2.7.1",
"start-server-and-test": "1.14.0",
"timekeeper": "2.2.0",
"ts-jest": "28.0.8",
"ts-jest": "29.0.0",
"ts-node": "10.8.1",
"tsconfig-paths": "4.0.0",
"typescript": "4.7.3"

View File

@ -0,0 +1,22 @@
#!/usr/bin/env node
const path = require("path")
const fs = require("fs")
function init() {
const envFilePath = path.join(process.cwd(), ".env")
if (!fs.existsSync(envFilePath)) {
const envFileJson = {
BUDIBASE_URL: "http://localhost:10000",
ACCOUNT_PORTAL_URL: "http://localhost:10001",
BB_ADMIN_USER_EMAIL: "admin",
BB_ADMIN_USER_PASSWORD: "admin",
}
let envFile = ""
Object.keys(envFileJson).forEach(key => {
envFile += `${key}=${envFileJson[key]}\n`
})
fs.writeFileSync(envFilePath, envFile)
}
}
init()

View File

@ -1,11 +0,0 @@
// 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")
const tk = require("timekeeper")
tk.freeze(MOCK_DATE)
if (!process.env.DEBUG) {
global.console.log = jest.fn() // console.log are ignored in tests
}
jest.setTimeout(60000)

View File

@ -0,0 +1,16 @@
import AccountInternalAPIClient from "./AccountInternalAPIClient"
import { AccountAPI, LicenseAPI } from "./apis"
import { State } from "../../types"
export default class AccountInternalAPI {
client: AccountInternalAPIClient
accounts: AccountAPI
licenses: LicenseAPI
constructor(state: State) {
this.client = new AccountInternalAPIClient(state)
this.accounts = new AccountAPI(this.client)
this.licenses = new LicenseAPI(this.client)
}
}

View File

@ -0,0 +1,70 @@
import env from "../../environment"
import fetch, { HeadersInit } from "node-fetch"
import { State } from "../../types"
type APIMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"
interface ApiOptions {
method?: APIMethod
body?: object
headers?: HeadersInit | undefined
}
export default class AccountInternalAPIClient {
state: State
host: string
constructor(state: State) {
if (!env.ACCOUNT_PORTAL_URL) {
throw new Error("Must set ACCOUNT_PORTAL_URL env var")
}
this.host = `${env.ACCOUNT_PORTAL_URL}`
this.state = state
}
apiCall =
(method: APIMethod) =>
async (url = "", options: ApiOptions = {}) => {
const requestOptions = {
method,
body: JSON.stringify(options.body),
headers: {
"Content-Type": "application/json",
Accept: "application/json",
cookie: this.state.cookie,
redirect: "follow",
follow: 20,
...options.headers,
},
credentials: "include",
}
// @ts-ignore
const response = await fetch(`${this.host}${url}`, requestOptions)
let body: any
const contentType = response.headers.get("content-type")
if (contentType && contentType.includes("application/json")) {
body = await response.json()
} else {
body = await response.text()
}
const message = `${method} ${url} - ${response.status}
Response body: ${JSON.stringify(body)}
Request body: ${requestOptions.body}`
if (response.status > 499) {
console.error(message)
} else if (response.status >= 400) {
console.warn(message)
}
return [response, body]
}
post = this.apiCall("POST")
get = this.apiCall("GET")
patch = this.apiCall("PATCH")
del = this.apiCall("DELETE")
put = this.apiCall("PUT")
}

View File

@ -0,0 +1,61 @@
import { Response } from "node-fetch"
import { Account, CreateAccountRequest } from "@budibase/types"
import AccountInternalAPIClient from "../AccountInternalAPIClient"
import { APIRequestOpts } from "../../../types"
export default class AccountAPI {
client: AccountInternalAPIClient
constructor(client: AccountInternalAPIClient) {
this.client = client
}
async validateEmail(
email: string,
opts: APIRequestOpts = { doExpect: true }
): Promise<Response> {
const [response, json] = await this.client.post(
`/api/accounts/validate/email`,
{
body: { email },
}
)
if (opts.doExpect) {
expect(response).toHaveStatusCode(200)
}
return response
}
async validateTenantId(
tenantId: string,
opts: APIRequestOpts = { doExpect: true }
): Promise<Response> {
const [response, json] = await this.client.post(
`/api/accounts/validate/tenantId`,
{
body: { tenantId },
}
)
if (opts.doExpect) {
expect(response).toHaveStatusCode(200)
}
return response
}
async create(
body: CreateAccountRequest,
opts: APIRequestOpts = { doExpect: true }
): Promise<[Response, Account]> {
const headers = {
"no-verify": "1",
}
const [response, json] = await this.client.post(`/api/accounts`, {
body,
headers,
})
if (opts.doExpect) {
expect(response).toHaveStatusCode(201)
}
return [response, json]
}
}

View File

@ -0,0 +1,9 @@
import AccountInternalAPIClient from "../AccountInternalAPIClient"
export default class LicenseAPI {
client: AccountInternalAPIClient
constructor(client: AccountInternalAPIClient) {
this.client = client
}
}

View File

@ -0,0 +1,2 @@
export { default as AccountAPI } from "./AccountAPI"
export { default as LicenseAPI } from "./LicenseAPI"

View File

@ -0,0 +1 @@
export { default as AccountInternalAPI } from "./AccountInternalAPI"

View File

@ -0,0 +1 @@
export * from "./api"

View File

@ -1,73 +0,0 @@
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 InternalAPIClient {
host: string
tenantName?: string
appId?: string
cookie?: string
constructor(appId?: string) {
if (!env.BUDIBASE_HOST) {
throw new Error("Must set BUDIBASE_HOST env var")
}
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,
redirect: "follow",
follow: 20,
...options.headers,
},
credentials: "include",
}
// prettier-ignore
// @ts-ignore
const response = await fetch(`https://${process.env.TENANT_ID}.${this.host}${url}`, requestOptions)
if (
response.status == 404 ||
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
}
post = this.apiCall("POST")
get = this.apiCall("GET")
patch = this.apiCall("PATCH")
del = this.apiCall("DELETE")
put = this.apiCall("PUT")
}
export default InternalAPIClient

View File

@ -1,38 +0,0 @@
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<Response> {
const response = await this.api.post(`/accounts/validate/email`, {
body: { email },
})
expect(response).toHaveStatusCode(200)
return response
}
async validateTenantId(tenantId: string): Promise<Response> {
const response = await this.api.post(`/accounts/validate/tenantId`, {
body: { tenantId },
})
expect(response).toHaveStatusCode(200)
return response
}
async create(body: Partial<NewAccount>): 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]
}
}

View File

@ -1,62 +0,0 @@
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_ACCOUNTS_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:", 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

View File

@ -1,178 +0,0 @@
import { Application } from "@budibase/server/api/controllers/public/mapping/types"
import { App } from "@budibase/types"
import { Response } from "node-fetch"
import InternalAPIClient from "./InternalAPIClient"
import { RouteConfig } from "../fixtures/types/routing"
import { AppPackageResponse } from "../fixtures/types/appPackage"
import { DeployConfig } from "../fixtures/types/deploy"
import { responseMessage } from "../fixtures/types/responseMessage"
import { UnpublishAppResponse } from "../fixtures/types/unpublishAppResponse"
export default class AppApi {
api: InternalAPIClient
constructor(apiClient: InternalAPIClient) {
this.api = apiClient
}
// TODO Fix the fetch apps to receive an optional number of apps and compare if the received app is more or less.
// each possible scenario should have its own method.
async fetchEmptyAppList(): Promise<[Response, Application[]]> {
const response = await this.api.get(`/applications?status=all`)
const json = await response.json()
expect(response).toHaveStatusCode(200)
expect(json.length).toBeGreaterThanOrEqual(0)
return [response, json]
}
async fetchAllApplications(): Promise<[Response, Application[]]> {
const response = await this.api.get(`/applications?status=all`)
const json = await response.json()
expect(response).toHaveStatusCode(200)
expect(json.length).toBeGreaterThanOrEqual(1)
return [response, json]
}
async canRender(): Promise<[Response, boolean]> {
const response = await this.api.get("/routing/client")
expect(response).toHaveStatusCode(200)
const json = await response.json()
const publishedAppRenders = Object.keys(json.routes).length > 0
expect(publishedAppRenders).toBe(true)
return [response, publishedAppRenders]
}
async getAppPackage(appId: string): Promise<[Response, AppPackageResponse]> {
const response = await this.api.get(`/applications/${appId}/appPackage`)
const json = await response.json()
expect(response).toHaveStatusCode(200)
expect(json.application.appId).toEqual(appId)
return [response, json]
}
async publish(appId: string | undefined): Promise<[Response, DeployConfig]> {
const response = await this.api.post(`/applications/${appId}/publish`)
const json = await response.json()
expect(response).toHaveStatusCode(200)
return [response, json]
}
async create(body: any): Promise<Partial<App>> {
const response = await this.api.post(`/applications`, { body })
const json = await response.json()
expect(response).toHaveStatusCode(200)
expect(json._id).toBeDefined()
return json
}
async read(id: string): Promise<[Response, Application]> {
const response = await this.api.get(`/applications/${id}`)
const json = await response.json()
return [response, json.data]
}
async sync(appId: string): Promise<[Response, responseMessage]> {
const response = await this.api.post(`/applications/${appId}/sync`)
const json = await response.json()
expect(response).toHaveStatusCode(200)
return [response, json]
}
// TODO
async updateClient(
appId: string,
body: any
): Promise<[Response, Application]> {
const response = await this.api.put(
`/applications/${appId}/client/update`,
{ body }
)
const json = await response.json()
return [response, json]
}
async revertPublished(appId: string): Promise<[Response, responseMessage]> {
const response = await this.api.post(`/dev/${appId}/revert`)
const json = await response.json()
expect(response).toHaveStatusCode(200)
expect(json).toEqual({
message: "Reverted changes successfully.",
})
return [response, json]
}
async revertUnpublished(appId: string): Promise<[Response, responseMessage]> {
const response = await this.api.post(`/dev/${appId}/revert`)
const json = await response.json()
expect(response).toHaveStatusCode(400)
expect(json).toEqual({
message: "App has not yet been deployed",
status: 400,
})
return [response, json]
}
async delete(appId: string): Promise<Response> {
const response = await this.api.del(`/applications/${appId}`)
expect(response).toHaveStatusCode(200)
return response
}
async rename(
appId: string,
oldName: string,
body: any
): Promise<[Response, Application]> {
const response = await this.api.put(`/applications/${appId}`, { body })
const json = await response.json()
expect(response).toHaveStatusCode(200)
expect(json.name).not.toEqual(oldName)
return [response, json]
}
async addScreentoApp(body: any): Promise<[Response, Application]> {
const response = await this.api.post(`/screens`, { body })
const json = await response.json()
return [response, json]
}
async getRoutes(screenExists?: boolean): Promise<[Response, RouteConfig]> {
const response = await this.api.get(`/routing`)
const json = await response.json()
expect(response).toHaveStatusCode(200)
if (screenExists) {
expect(json.routes["/test"]).toBeTruthy()
} else {
expect(json.routes["/test"]).toBeUndefined()
}
return [response, json]
}
async unpublish(appId: string): Promise<[Response]> {
const response = await this.api.post(`/applications/${appId}/unpublish`)
expect(response).toHaveStatusCode(204)
return [response]
}
async unlock(appId: string): Promise<[Response, responseMessage]> {
const response = await this.api.del(`/dev/${appId}/lock`)
const json = await response.json()
expect(response).toHaveStatusCode(200)
expect(json.message).toEqual("Lock released successfully.")
return [response, json]
}
async updateIcon(appId: string): Promise<[Response, Application]> {
const body = {
icon: {
name: "ConversionFunnel",
color: "var(--spectrum-global-color-red-400)",
},
}
const response = await this.api.put(`/applications/${appId}`, { body })
const json = await response.json()
expect(response).toHaveStatusCode(200)
expect(json.icon.name).toEqual(body.icon.name)
expect(json.icon.color).toEqual(body.icon.color)
return [response, json]
}
}

View File

@ -1,39 +0,0 @@
import { Response } from "node-fetch"
import InternalAPIClient from "./InternalAPIClient"
export default class AuthApi {
api: InternalAPIClient
constructor(apiClient: InternalAPIClient) {
this.api = apiClient
}
async loginAsAdmin(): Promise<[Response, any]> {
const response = await this.api.post(`/global/auth/default/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<any> {
return this.api.post(`/global/auth/logout`)
}
}

View File

@ -1,3 +0,0 @@
const Chance = require("chance")
export default new Chance()

View File

@ -1,79 +0,0 @@
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"
import UserManagementApi from "./userManagement"
import AccountsApi from "./accounts"
import { generateAccount } from "../fixtures/accounts"
export default class TestConfiguration<T> {
applications: ApplicationApi
auth: AuthApi
screen: ScreenApi
context: T
tables: TablesApi
rows: RowApi
users: UserManagementApi
accounts: AccountsApi
apiClient: InternalAPIClient
accountsApiClient: AccountsApiClient
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 = <T>{}
}
async loginAsAdmin() {
await this.auth.login(
<string>process.env.BB_ADMIN_USER_EMAIL,
<string>process.env.BB_ADMIN_USER_PASSWORD
)
}
// TODO: add logic to setup or login based in env variables
async setupAccountAndTenant() {
const account = generateAccount()
await this.accounts.validateEmail(<string>account.email)
await this.accounts.validateTenantId(<string>account.tenantId)
process.env.TENANT_ID = <string>account.tenantId
await this.accounts.create(account)
await this.updateApiClients(<string>account.tenantName)
await this.auth.login(<string>account.email, <string>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 = <T>{}
}
async login(email: string, password: string) {
await this.auth.logout()
await this.auth.login(email, password)
}
async afterAll() {
this.context = <T>{}
await this.auth.logout()
}
}

View File

@ -1,3 +0,0 @@
export interface responseMessage {
message: string
}

View File

@ -1,77 +0,0 @@
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 PublicAPIClient {
host: string
apiKey?: string
tenantName?: string
appId?: string
cookie?: string
constructor(appId?: string) {
if (!env.BUDIBASE_HOST) {
throw new Error("Must set BUDIBASE_HOST env var")
}
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 = {}) => {
const requestOptions = {
method,
body: JSON.stringify(options.body),
headers: {
"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(`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
}
post = this.apiCall("POST")
get = this.apiCall("GET")
patch = this.apiCall("PATCH")
del = this.apiCall("DELETE")
put = this.apiCall("PUT")
}
export default PublicAPIClient

View File

@ -1,38 +0,0 @@
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<Response> {
const response = await this.api.post(`/accounts/validate/email`, {
body: { email },
})
expect(response).toHaveStatusCode(200)
return response
}
async validateTenantId(tenantId: string): Promise<Response> {
const response = await this.api.post(`/accounts/validate/tenantId`, {
body: { tenantId },
})
expect(response).toHaveStatusCode(200)
return response
}
async create(body: Partial<NewAccount>): 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]
}
}

View File

@ -1,66 +0,0 @@
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_ACCOUNTS_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

View File

@ -1,77 +0,0 @@
import PublicAPIClient from "./PublicAPIClient"
import {
Application,
SearchInputParams,
CreateApplicationParams,
} from "@budibase/server/api/controllers/public/mapping/types"
import { Response } from "node-fetch"
import generateApp from "../fixtures/applications"
export default class AppApi {
api: PublicAPIClient
constructor(apiClient: PublicAPIClient) {
this.api = apiClient
}
async seed(): Promise<[Response, Application]> {
return this.create(generateApp())
}
async create(
body: CreateApplicationParams
): Promise<[Response, Application]> {
const response = await this.api.post(`/applications`, { body })
const json = await response.json()
return [response, json.data]
}
async read(id: string): Promise<[Response, Application]> {
const response = await this.api.get(`/applications/${id}`)
const json = await response.json()
return [response, json.data]
}
async search(body: SearchInputParams): Promise<[Response, [Application]]> {
const response = await this.api.post(`/applications/search`, { body })
const json = await response.json()
return [response, json.data]
}
async update(
id: string,
body: Application
): Promise<[Response, Application]> {
const response = await this.api.put(`/applications/${id}`, { body })
const json = await response.json()
return [response, json.data]
}
async delete(id: string): Promise<[Response, Application]> {
const response = await this.api.del(`/applications/${id}`)
const json = await response.json()
return [response, json.data]
}
async publish(id: string): Promise<[Response, any]> {
const response = await this.api.post(`/applications/${id}/publish`)
const json = await response.json()
return [response, json.data]
}
async unpublish(id: string): Promise<[Response]> {
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)
}
}

View File

@ -1,48 +0,0 @@
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<any> {
return this.api.post(`/global/auth/logout`)
}
async getApiKey(): Promise<ApiKeyResponse> {
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
}
}

View File

@ -1,90 +0,0 @@
import PublicAPIClient from "./PublicAPIClient"
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<T> {
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,
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 = <T>{}
}
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(<string>account.email)
await this.accounts.validateTenantId(<string>account.tenantId)
process.env.TENANT_ID = <string>account.tenantId
await this.accounts.create(account)
await this.updateApiClients(<string>account.tenantName)
await this.auth.login(<string>account.email, <string>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 = <T>{}
}
async beforeAll() {}
async afterAll() {
this.context = <T>{}
}
}

View File

@ -1,53 +0,0 @@
import PublicAPIClient from "./PublicAPIClient"
import {
CreateRowParams,
Row,
SearchInputParams,
} from "@budibase/server/api/controllers/public/mapping/types"
import { HeadersInit, Response } from "node-fetch"
import { generateRow } from "../fixtures/tables"
export default class RowApi {
api: PublicAPIClient
headers?: HeadersInit
tableId?: string
constructor(apiClient: PublicAPIClient) {
this.api = apiClient
}
async seed(tableId: string) {
return this.create(generateRow({ tableId }))
}
async create(body: CreateRowParams): Promise<[Response, Row]> {
const response = await this.api.post(`/tables/${this.tableId}/rows`, {
body,
})
const json = await response.json()
return [response, json.data]
}
async read(id: string): Promise<[Response, Row]> {
const response = await this.api.get(`/tables/${this.tableId}/rows/${id}`)
const json = await response.json()
return [response, json.data]
}
async search(body: SearchInputParams): Promise<[Response, [Row]]> {
const response = await this.api.post(
`/tables/${this.tableId}/rows/search`,
{ body }
)
const json = await response.json()
return [response, json.data]
}
async update(id: string, body: Row): Promise<[Response, Row]> {
const response = await this.api.put(`/tables/${this.tableId}/rows/${id}`, {
body,
})
const json = await response.json()
return [response, json.data]
}
}

View File

@ -1,44 +0,0 @@
import PublicAPIClient from "./PublicAPIClient"
import {
CreateUserParams,
SearchInputParams,
User,
} from "@budibase/server/api/controllers/public/mapping/types"
import { Response } from "node-fetch"
import generateUser from "../fixtures/users"
export default class UserApi {
api: PublicAPIClient
constructor(apiClient: PublicAPIClient) {
this.api = apiClient
}
async seed() {
return this.create(generateUser())
}
async create(body: CreateUserParams): Promise<[Response, User]> {
const response = await this.api.post(`/users`, { body })
const json = await response.json()
return [response, json.data]
}
async read(id: string): Promise<[Response, User]> {
const response = await this.api.get(`/users/${id}`)
const json = await response.json()
return [response, json.data]
}
async search(body: SearchInputParams): Promise<[Response, [User]]> {
const response = await this.api.post(`/users/search`, { body })
const json = await response.json()
return [response, json.data]
}
async update(id: string, body: User): Promise<[Response, User]> {
const response = await this.api.put(`/users/${id}`, { body })
const json = await response.json()
return [response, json.data]
}
}

View File

@ -1,22 +0,0 @@
import { NewAccount } from "./types/newAccount"
import generator from "../../generator"
import { Hosting } from "@budibase/types"
export const generateAccount = (): Partial<NewAccount> => {
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}`,
}
}

View File

@ -1,15 +0,0 @@
import generator from "../../generator"
import {
Application,
CreateApplicationParams,
} from "@budibase/server/api/controllers/public/mapping/types"
const generate = (
overrides: Partial<Application> = {}
): CreateApplicationParams => ({
name: generator.word(),
url: `/${generator.word()}`,
...overrides,
})
export default generate

View File

@ -1,5 +0,0 @@
import { Account } from "@budibase/types"
export interface NewAccount extends Account {
password: string
}

View File

@ -1,12 +1,18 @@
import { join } from "path"
let LOADED = false
if (!LOADED) {
require("dotenv").config({
path: join(__dirname, "..", ".env"),
})
LOADED = true
}
const env = {
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
},
BUDIBASE_URL: process.env.BUDIBASE_URL,
ACCOUNT_PORTAL_URL: process.env.ACCOUNT_PORTAL_URL,
BB_ADMIN_USER_EMAIL: process.env.BB_ADMIN_USER_EMAIL,
BB_ADMIN_USER_PASSWORD: process.env.BB_ADMIN_USER_PASSWORD,
}
export = env

View File

@ -0,0 +1,39 @@
import AppAPI from "./apis/AppAPI"
import AuthAPI from "./apis/AuthAPI"
import EnvironmentAPI from "./apis/EnvironmentAPI"
import RoleAPI from "./apis/RoleAPI"
import RowAPI from "./apis/RowAPI"
import ScreenAPI from "./apis/ScreenAPI"
import SelfAPI from "./apis/SelfAPI"
import TableAPI from "./apis/TableAPI"
import UserAPI from "./apis/UserAPI"
import BudibaseInternalAPIClient from "./BudibaseInternalAPIClient"
import { State } from "../../types"
export default class BudibaseInternalAPI {
client: BudibaseInternalAPIClient
apps: AppAPI
auth: AuthAPI
environment: EnvironmentAPI
roles: RoleAPI
rows: RowAPI
screens: ScreenAPI
self: SelfAPI
tables: TableAPI
users: UserAPI
constructor(state: State) {
this.client = new BudibaseInternalAPIClient(state)
this.apps = new AppAPI(this.client)
this.auth = new AuthAPI(this.client, state)
this.environment = new EnvironmentAPI(this.client)
this.roles = new RoleAPI(this.client)
this.rows = new RowAPI(this.client)
this.screens = new ScreenAPI(this.client)
this.self = new SelfAPI(this.client)
this.tables = new TableAPI(this.client)
this.users = new UserAPI(this.client)
}
}

View File

@ -0,0 +1,76 @@
import env from "../../environment"
import fetch, { HeadersInit } from "node-fetch"
import { State } from "../../types"
type APIMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"
interface ApiOptions {
method?: APIMethod
body?: object
headers?: HeadersInit | undefined
}
class BudibaseInternalAPIClient {
host: string
state: State
constructor(state: State) {
if (!env.BUDIBASE_URL) {
throw new Error("Must set BUDIBASE_URL env var")
}
this.host = `${env.ACCOUNT_PORTAL_URL}/api`
this.host = `${env.BUDIBASE_URL}/api`
this.state = state
}
apiCall =
(method: APIMethod) =>
async (url = "", options: ApiOptions = {}) => {
const requestOptions = {
method,
body: JSON.stringify(options.body),
headers: {
"x-budibase-app-id": this.state.appId,
"Content-Type": "application/json",
Accept: "application/json",
cookie: this.state.cookie,
redirect: "follow",
follow: 20,
...options.headers,
},
credentials: "include",
}
// prettier-ignore
// @ts-ignore
const response = await fetch(`${this.host}${url}`, requestOptions)
let body: any
const contentType = response.headers.get("content-type")
if (contentType && contentType.includes("application/json")) {
body = await response.json()
} else {
body = await response.text()
}
const message = `${method} ${url} - ${response.status}
Response body: ${JSON.stringify(body)}
Request body: ${requestOptions.body}`
if (response.status > 499) {
console.error(message)
} else if (response.status >= 400) {
console.warn(message)
}
return [response, body]
}
post = this.apiCall("POST")
get = this.apiCall("GET")
patch = this.apiCall("PATCH")
del = this.apiCall("DELETE")
put = this.apiCall("PUT")
}
export default BudibaseInternalAPIClient

View File

@ -0,0 +1,173 @@
import { App } from "@budibase/types"
import { Response } from "node-fetch"
import {
RouteConfig,
AppPackageResponse,
DeployConfig,
MessageResponse,
} from "../../../types"
import BudibaseInternalAPIClient from "../BudibaseInternalAPIClient"
export default class AppAPI {
client: BudibaseInternalAPIClient
constructor(client: BudibaseInternalAPIClient) {
this.client = client
}
// TODO Fix the fetch apps to receive an optional number of apps and compare if the received app is more or less.
// each possible scenario should have its own method.
async fetchEmptyAppList(): Promise<[Response, App[]]> {
const [response, json] = await this.client.get(`/applications?status=all`)
expect(response).toHaveStatusCode(200)
expect(json.length).toBeGreaterThanOrEqual(0)
return [response, json]
}
async fetchAllApplications(): Promise<[Response, App[]]> {
const [response, json] = await this.client.get(`/applications?status=all`)
expect(response).toHaveStatusCode(200)
expect(json.length).toBeGreaterThanOrEqual(1)
return [response, json]
}
async canRender(): Promise<[Response, boolean]> {
const [response, json] = await this.client.get("/routing/client")
expect(response).toHaveStatusCode(200)
const publishedAppRenders = Object.keys(json.routes).length > 0
expect(publishedAppRenders).toBe(true)
return [response, publishedAppRenders]
}
async getAppPackage(appId: string): Promise<[Response, AppPackageResponse]> {
const [response, json] = await this.client.get(
`/applications/${appId}/appPackage`
)
expect(response).toHaveStatusCode(200)
expect(json.application.appId).toEqual(appId)
return [response, json]
}
async publish(appId: string | undefined): Promise<[Response, DeployConfig]> {
const [response, json] = await this.client.post(
`/applications/${appId}/publish`
)
expect(response).toHaveStatusCode(200)
return [response, json]
}
async create(body: any): Promise<App> {
const [response, json] = await this.client.post(`/applications`, { body })
expect(response).toHaveStatusCode(200)
expect(json._id).toBeDefined()
return json
}
async read(id: string): Promise<[Response, App]> {
const [response, json] = await this.client.get(`/applications/${id}`)
return [response, json.data]
}
async sync(appId: string): Promise<[Response, MessageResponse]> {
const [response, json] = await this.client.post(
`/applications/${appId}/sync`
)
expect(response).toHaveStatusCode(200)
return [response, json]
}
// TODO
async updateClient(appId: string, body: any): Promise<[Response, App]> {
const [response, json] = await this.client.put(
`/applications/${appId}/client/update`,
{ body }
)
return [response, json]
}
async revertPublished(appId: string): Promise<[Response, MessageResponse]> {
const [response, json] = await this.client.post(`/dev/${appId}/revert`)
expect(response).toHaveStatusCode(200)
expect(json).toEqual({
message: "Reverted changes successfully.",
})
return [response, json]
}
async revertUnpublished(appId: string): Promise<[Response, MessageResponse]> {
const [response, json] = await this.client.post(`/dev/${appId}/revert`)
expect(response).toHaveStatusCode(400)
expect(json).toEqual({
message: "App has not yet been deployed",
status: 400,
})
return [response, json]
}
async delete(appId: string): Promise<Response> {
const [response, _] = await this.client.del(`/applications/${appId}`)
expect(response).toHaveStatusCode(200)
return response
}
async rename(
appId: string,
oldName: string,
body: any
): Promise<[Response, App]> {
const [response, json] = await this.client.put(`/applications/${appId}`, {
body,
})
expect(response).toHaveStatusCode(200)
expect(json.name).not.toEqual(oldName)
return [response, json]
}
async addScreentoApp(body: any): Promise<[Response, App]> {
const [response, json] = await this.client.post(`/screens`, { body })
return [response, json]
}
async getRoutes(screenExists?: boolean): Promise<[Response, RouteConfig]> {
const [response, json] = await this.client.get(`/routing`)
expect(response).toHaveStatusCode(200)
if (screenExists) {
expect(json.routes["/test"]).toBeTruthy()
} else {
expect(json.routes["/test"]).toBeUndefined()
}
return [response, json]
}
async unpublish(appId: string): Promise<[Response]> {
const [response, json] = await this.client.post(
`/applications/${appId}/unpublish`
)
expect(response).toHaveStatusCode(204)
return [response]
}
async unlock(appId: string): Promise<[Response, MessageResponse]> {
const [response, json] = await this.client.del(`/dev/${appId}/lock`)
expect(response).toHaveStatusCode(200)
expect(json.message).toEqual("Lock released successfully.")
return [response, json]
}
async updateIcon(appId: string): Promise<[Response, App]> {
const body = {
icon: {
name: "ConversionFunnel",
color: "var(--spectrum-global-color-red-400)",
},
}
const [response, json] = await this.client.put(`/applications/${appId}`, {
body,
})
expect(response).toHaveStatusCode(200)
expect(json.icon.name).toEqual(body.icon.name)
expect(json.icon.color).toEqual(body.icon.color)
return [response, json]
}
}

View File

@ -0,0 +1,39 @@
import { Response } from "node-fetch"
import BudibaseInternalAPIClient from "../BudibaseInternalAPIClient"
import { APIRequestOpts, State } from "../../../types"
export default class AuthAPI {
state: State
client: BudibaseInternalAPIClient
constructor(client: BudibaseInternalAPIClient, state: State) {
this.client = client
this.state = state
}
async login(
tenantId: string,
email: String,
password: String,
opts: APIRequestOpts = { doExpect: true }
): Promise<[Response, string]> {
const [response, json] = await this.client.post(
`/global/auth/${tenantId}/login`,
{
body: {
username: email,
password: password,
},
}
)
if (opts.doExpect) {
expect(response).toHaveStatusCode(200)
}
const cookie = response.headers.get("set-cookie")
return [response, cookie!]
}
async logout(): Promise<any> {
return this.client.post(`/global/auth/logout`)
}
}

View File

@ -0,0 +1,21 @@
import { GetEnvironmentResponse } from "@budibase/types"
import BudibaseInternalAPIClient from "../BudibaseInternalAPIClient"
import { APIRequestOpts } from "../../../types"
export default class EnvironmentAPI {
client: BudibaseInternalAPIClient
constructor(client: BudibaseInternalAPIClient) {
this.client = client
}
async getEnvironment(
opts: APIRequestOpts = { doExpect: true }
): Promise<GetEnvironmentResponse> {
const [response, json] = await this.client.get(`/system/environment`)
if (opts.doExpect) {
expect(response.status).toBe(200)
}
return json
}
}

View File

@ -0,0 +1,23 @@
import { Response } from "node-fetch"
import { Role, UserRoles } from "@budibase/types"
import BudibaseInternalAPIClient from "../BudibaseInternalAPIClient"
export default class RoleAPI {
client: BudibaseInternalAPIClient
constructor(client: BudibaseInternalAPIClient) {
this.client = client
}
async getRoles(): Promise<[Response, Role[]]> {
const [response, json] = await this.client.get(`/roles`)
expect(response).toHaveStatusCode(200)
return [response, json]
}
async createRole(body: Partial<UserRoles>): Promise<[Response, UserRoles]> {
const [response, json] = await this.client.post(`/roles`, { body })
expect(response).toHaveStatusCode(200)
return [response, json]
}
}

View File

@ -1,18 +1,18 @@
import { Response } from "node-fetch"
import { Row } from "@budibase/types"
import InternalAPIClient from "./InternalAPIClient"
import BudibaseInternalAPIClient from "../BudibaseInternalAPIClient"
export default class RowsApi {
api: InternalAPIClient
export default class RowAPI {
rowAdded: boolean
constructor(apiClient: InternalAPIClient) {
this.api = apiClient
client: BudibaseInternalAPIClient
constructor(client: BudibaseInternalAPIClient) {
this.client = client
this.rowAdded = false
}
async getAll(tableId: string): Promise<[Response, Row[]]> {
const response = await this.api.get(`/${tableId}/rows`)
const json = await response.json()
const [response, json] = await this.client.get(`/${tableId}/rows`)
if (this.rowAdded) {
expect(response).toHaveStatusCode(200)
expect(json.length).toBeGreaterThanOrEqual(1)
@ -20,8 +20,9 @@ export default class RowsApi {
return [response, json]
}
async add(tableId: string, body: any): Promise<[Response, Row]> {
const response = await this.api.post(`/${tableId}/rows`, { body })
const json = await response.json()
const [response, json] = await this.client.post(`/${tableId}/rows`, {
body,
})
expect(response).toHaveStatusCode(200)
expect(json._id).toBeDefined()
expect(json._rev).toBeDefined()
@ -31,8 +32,9 @@ export default class RowsApi {
}
async delete(tableId: string, body: any): Promise<[Response, Row[]]> {
const response = await this.api.del(`/${tableId}/rows/`, { body })
const json = await response.json()
const [response, json] = await this.client.del(`/${tableId}/rows/`, {
body,
})
expect(response).toHaveStatusCode(200)
return [response, json]
}
@ -41,8 +43,9 @@ export default class RowsApi {
tableId: string,
body: any
): Promise<[Response, Row[]]> {
const response = await this.api.post(`/${tableId}/search`, { body })
const json = await response.json()
const [response, json] = await this.client.post(`/${tableId}/search`, {
body,
})
expect(response).toHaveStatusCode(200)
expect(json.hasNextPage).toEqual(false)
return [response, json.rows]
@ -52,8 +55,9 @@ export default class RowsApi {
tableId: string,
body: any
): Promise<[Response, Row[]]> {
const response = await this.api.post(`/${tableId}/search`, { body })
const json = await response.json()
const [response, json] = await this.client.post(`/${tableId}/search`, {
body,
})
expect(response).toHaveStatusCode(200)
expect(json.hasNextPage).toEqual(true)
expect(json.rows.length).toEqual(10)

View File

@ -1,17 +1,16 @@
import { Screen } from "@budibase/types"
import { Response } from "node-fetch"
import InternalAPIClient from "./InternalAPIClient"
import BudibaseInternalAPIClient from "../BudibaseInternalAPIClient"
export default class ScreenApi {
api: InternalAPIClient
export default class ScreenAPI {
client: BudibaseInternalAPIClient
constructor(apiClient: InternalAPIClient) {
this.api = apiClient
constructor(client: BudibaseInternalAPIClient) {
this.client = client
}
async create(body: any): Promise<[Response, Screen]> {
const response = await this.api.post(`/screens`, { body })
const json = await response.json()
const [response, json] = await this.client.post(`/screens`, { body })
expect(response).toHaveStatusCode(200)
expect(json._id).toBeDefined()
expect(json.routing.roleId).toBe(body.routing.roleId)
@ -19,8 +18,9 @@ export default class ScreenApi {
}
async delete(screenId: string, rev: string): Promise<[Response, Screen]> {
const response = await this.api.del(`/screens/${screenId}/${rev}`)
const json = await response.json()
const [response, json] = await this.client.del(
`/screens/${screenId}/${rev}`
)
expect(response).toHaveStatusCode(200)
return [response, json]
}

View File

@ -0,0 +1,33 @@
import { Response } from "node-fetch"
import { User } from "@budibase/types"
import BudibaseInternalAPIClient from "../BudibaseInternalAPIClient"
import { ApiKeyResponse } from "../../../types"
export default class SelfAPI {
client: BudibaseInternalAPIClient
constructor(client: BudibaseInternalAPIClient) {
this.client = client
}
async getSelf(): Promise<[Response, Partial<User>]> {
const [response, json] = await this.client.get(`/global/self`)
expect(response).toHaveStatusCode(200)
return [response, json]
}
async changeSelfPassword(body: Partial<User>): Promise<[Response, User]> {
const [response, json] = await this.client.post(`/global/self`, { body })
expect(response).toHaveStatusCode(200)
expect(json._id).toEqual(body._id)
expect(json._rev).not.toEqual(body._rev)
return [response, json]
}
async getApiKey(): Promise<ApiKeyResponse> {
const [response, json] = await this.client.get(`/global/self/api_key`)
expect(response).toHaveStatusCode(200)
expect(json).toHaveProperty("apiKey")
return json
}
}

View File

@ -1,34 +1,31 @@
import { Response } from "node-fetch"
import { Table } from "@budibase/types"
import InternalAPIClient from "./InternalAPIClient"
import { responseMessage } from "../fixtures/types/responseMessage"
import BudibaseInternalAPIClient from "../BudibaseInternalAPIClient"
import { MessageResponse } from "../../../types"
export default class TablesApi {
api: InternalAPIClient
export default class TableAPI {
client: BudibaseInternalAPIClient
constructor(apiClient: InternalAPIClient) {
this.api = apiClient
constructor(client: BudibaseInternalAPIClient) {
this.client = client
}
async getAll(expectedNumber: Number): Promise<[Response, Table[]]> {
const response = await this.api.get(`/tables`)
const json = await response.json()
const [response, json] = await this.client.get(`/tables`)
expect(response).toHaveStatusCode(200)
expect(json.length).toBe(expectedNumber)
return [response, json]
}
async getTableById(id: string): Promise<[Response, Table]> {
const response = await this.api.get(`/tables/${id}`)
const json = await response.json()
const [response, json] = await this.client.get(`/tables/${id}`)
expect(response).toHaveStatusCode(200)
expect(json._id).toEqual(id)
return [response, json]
}
async save(body: any, columnAdded?: boolean): Promise<[Response, Table]> {
const response = await this.api.post(`/tables`, { body })
const json = await response.json()
const [response, json] = await this.client.post(`/tables`, { body })
expect(response).toHaveStatusCode(200)
expect(json._id).toBeDefined()
expect(json._rev).toBeDefined()
@ -40,8 +37,7 @@ export default class TablesApi {
}
async forbiddenSave(body: any): Promise<[Response, Table]> {
const response = await this.api.post(`/tables`, { body })
const json = await response.json()
const [response, json] = await this.client.post(`/tables`, { body })
expect(response).toHaveStatusCode(403)
return [response, json]
@ -50,9 +46,8 @@ export default class TablesApi {
async delete(
id: string,
revId: string
): Promise<[Response, responseMessage]> {
const response = await this.api.del(`/tables/${id}/${revId}`)
const json = await response.json()
): Promise<[Response, MessageResponse]> {
const [response, json] = await this.client.del(`/tables/${id}/${revId}`)
expect(response).toHaveStatusCode(200)
expect(json.message).toEqual(`Table ${id} deleted.`)
return [response, json]

View File

@ -1,33 +1,30 @@
import { Response } from "node-fetch"
import { Role, User, UserDeletedEvent, UserRoles } from "@budibase/types"
import InternalAPIClient from "./InternalAPIClient"
import { responseMessage } from "../fixtures/types/responseMessage"
import BudibaseInternalAPIClient from "../BudibaseInternalAPIClient"
import { MessageResponse } from "../../../types"
export default class UserManagementApi {
api: InternalAPIClient
export default class UserAPI {
client: BudibaseInternalAPIClient
constructor(apiClient: InternalAPIClient) {
this.api = apiClient
constructor(client: BudibaseInternalAPIClient) {
this.client = client
}
async search(): Promise<[Response, Partial<User>[]]> {
const response = await this.api.post(`/global/users/search`, {})
const json = await response.json()
const [response, json] = await this.client.post(`/global/users/search`, {})
expect(response).toHaveStatusCode(200)
expect(json.data.length).toBeGreaterThan(0)
return [response, json]
}
async getSelf(): Promise<[Response, Partial<User>]> {
const response = await this.api.get(`/global/self`)
const json = await response.json()
const [response, json] = await this.client.get(`/global/self`)
expect(response).toHaveStatusCode(200)
return [response, json]
}
async getAll(): Promise<[Response, Partial<User>[]]> {
const response = await this.api.get(`/global/users`)
const json = await response.json()
const [response, json] = await this.client.get(`/global/users`)
expect(response).toHaveStatusCode(200)
expect(json.length).toBeGreaterThan(0)
return [response, json]
@ -41,22 +38,24 @@ export default class UserManagementApi {
groups: [],
},
}
const response = await this.api.post(`/global/users/bulk`, { body })
const json = await response.json()
const [response, json] = await this.client.post(`/global/users/bulk`, {
body,
})
expect(response).toHaveStatusCode(200)
expect(json.created.unsuccessful.length).toEqual(0)
expect(json.created.successful.length).toEqual(body.create.users.length)
return [response, json]
}
async deleteMultiple(userId: string[]): Promise<[Response, responseMessage]> {
async deleteMultiple(userId: string[]): Promise<[Response, MessageResponse]> {
const body = {
delete: {
userIds: [userId],
},
}
const response = await this.api.post(`/global/users/bulk`, { body })
const json = await response.json()
const [response, json] = await this.client.post(`/global/users/bulk`, {
body,
})
expect(response).toHaveStatusCode(200)
expect(json.deleted.successful.length).toEqual(1)
expect(json.deleted.unsuccessful.length).toEqual(0)
@ -64,16 +63,17 @@ export default class UserManagementApi {
return [response, json]
}
async delete(userId: string): Promise<[Response, UserDeletedEvent]> {
const response = await this.api.del(`/global/users/${userId}`)
const json = await response.json()
const [response, json] = await this.client.del(`/global/users/${userId}`)
expect(response).toHaveStatusCode(200)
expect(json.message).toEqual(`User ${userId} deleted.`)
return [response, json]
}
async invite(body: any): Promise<[Response, responseMessage]> {
const response = await this.api.post(`/global/users/multi/invite`, { body })
const json = await response.json()
async invite(body: any): Promise<[Response, MessageResponse]> {
const [response, json] = await this.client.post(
`/global/users/multi/invite`,
{ body }
)
expect(response).toHaveStatusCode(200)
expect(json.unsuccessful.length).toEqual(0)
expect(json.successful.length).toEqual(body.length)
@ -82,15 +82,13 @@ export default class UserManagementApi {
}
async getRoles(): Promise<[Response, Role[]]> {
const response = await this.api.get(`/roles`)
const json = await response.json()
const [response, json] = await this.client.get(`/roles`)
expect(response).toHaveStatusCode(200)
return [response, json]
}
async updateInfo(body: any): Promise<[Response, User]> {
const response = await this.api.post(`/global/users/`, { body })
const json = await response.json()
const [response, json] = await this.client.post(`/global/users/`, { body })
expect(response).toHaveStatusCode(200)
expect(json._id).toEqual(body._id)
expect(json._rev).not.toEqual(body._rev)
@ -98,8 +96,7 @@ export default class UserManagementApi {
}
async forcePasswordReset(body: any): Promise<[Response, User]> {
const response = await this.api.post(`/global/users/`, { body })
const json = await response.json()
const [response, json] = await this.client.post(`/global/users/`, { body })
expect(response).toHaveStatusCode(200)
expect(json._id).toEqual(body._id)
expect(json._rev).not.toEqual(body._rev)
@ -107,15 +104,13 @@ export default class UserManagementApi {
}
async getInfo(userId: string): Promise<[Response, User]> {
const response = await this.api.get(`/global/users/${userId}`)
const json = await response.json()
const [response, json] = await this.client.get(`/global/users/${userId}`)
expect(response).toHaveStatusCode(200)
return [response, json]
}
async changeSelfPassword(body: Partial<User>): Promise<[Response, User]> {
const response = await this.api.post(`/global/self`, { body })
const json = await response.json()
const [response, json] = await this.client.post(`/global/self`, { body })
expect(response).toHaveStatusCode(200)
expect(json._id).toEqual(body._id)
expect(json._rev).not.toEqual(body._rev)
@ -123,8 +118,7 @@ export default class UserManagementApi {
}
async createRole(body: Partial<UserRoles>): Promise<[Response, UserRoles]> {
const response = await this.api.post(`/roles`, { body })
const json = await response.json()
const [response, json] = await this.client.post(`/roles`, { body })
expect(response).toHaveStatusCode(200)
return [response, json]
}

View File

@ -0,0 +1 @@
export { default as BudibaseInternalAPI } from "./BudibaseInternalAPI"

View File

@ -0,0 +1,25 @@
import { BudibaseInternalAPI } from "../api"
import { BudibaseTestConfiguration } from "../../shared"
export default class TestConfiguration<T> extends BudibaseTestConfiguration {
// apis
api: BudibaseInternalAPI
// context
context: T
constructor() {
super()
// for brevity
this.api = this.internalApi
this.context = <T>{}
}
async beforeAll() {
await super.beforeAll()
}
async afterAll() {
await super.afterAll()
}
}

View File

@ -0,0 +1 @@
export { default as TestConfiguration } from "./TestConfiguration"

View File

@ -0,0 +1,20 @@
import { generator } from "../../shared"
import { Hosting, CreateAccountRequest } from "@budibase/types"
export const generateAccount = (): CreateAccountRequest => {
const uuid = generator.guid()
const email = `qa+${uuid}@budibase.com`
const tenant = `tenant${uuid.replace(/-/g, "")}`
return {
email,
hosting: Hosting.CLOUD,
name: email,
password: uuid,
profession: "software_engineer",
size: "10+",
tenantId: tenant,
tenantName: tenant,
}
}

View File

@ -1,21 +1,20 @@
import generator from "../../generator"
import { Application } from "@budibase/server/api/controllers/public/mapping/types"
import { Template } from "@budibase/types"
import { App } from "@budibase/types"
import { generator } from "../../shared"
import { CreateAppRequest } from "../../types"
export const generateApp = (
overrides: Partial<Application> = {}
): Partial<Application> => ({
overrides: Partial<CreateAppRequest> = {}
): CreateAppRequest => ({
name: generator.word() + generator.hash(),
url: `/${generator.word() + generator.hash()}`,
...overrides,
})
// Applications type doesn't work here, save to add useTemplate parameter?
export const appFromTemplate = (): any => {
export const appFromTemplate = (): CreateAppRequest => {
return {
name: generator.word(),
url: `/${generator.word()}`,
// @ts-ignore
useTemplate: "true",
templateName: "Near Miss Register",
templateKey: "app/near-miss-register",

View File

@ -0,0 +1,6 @@
export * as accounts from "./accounts"
export * as apps from "./applications"
export * as rows from "./rows"
export * as screens from "./screens"
export * as tables from "./tables"
export * as users from "./users"

View File

@ -1,8 +1,8 @@
import generator from "../../generator"
import { generator } from "../../shared"
const randomId = generator.guid()
const generateScreen = (roleId: string): any => ({
export const generateScreen = (roleId: string): any => ({
showNavigation: true,
width: "Large",
name: randomId,
@ -30,5 +30,3 @@ const generateScreen = (roleId: string): any => ({
homeScreen: false,
},
})
export default generateScreen

View File

@ -1,10 +1,10 @@
import generator from "../../generator"
import { generator } from "../../shared"
import { User } from "@budibase/types"
const generateDeveloper = (): Partial<User> => {
const randomId = generator.guid()
return {
email: `pedro+${randomId}@budibase.com`,
email: `${randomId}@budibase.com`,
password: randomId,
roles: {},
forceResetPassword: true,
@ -17,7 +17,7 @@ const generateDeveloper = (): Partial<User> => {
const generateAdmin = (): Partial<User> => {
const randomId = generator.guid()
return {
email: `pedro+${randomId}@budibase.com`,
email: `${randomId}@budibase.com`,
password: randomId,
roles: {},
forceResetPassword: true,
@ -32,7 +32,7 @@ const generateAdmin = (): Partial<User> => {
const generateAppUser = (): Partial<User> => {
const randomId = generator.guid()
return {
email: `pedro+${randomId}@budibase.com`,
email: `${randomId}@budibase.com`,
password: randomId,
roles: {},
forceResetPassword: true,
@ -49,7 +49,7 @@ export const generateInviteUser = (): Object[] => {
const randomId = generator.guid()
return [
{
email: `pedro+${randomId}@budibase.com`,
email: `${randomId}@budibase.com`,
userInfo: {
userGroups: [],
},

View File

@ -0,0 +1 @@
export * from "./api"

View File

@ -0,0 +1,42 @@
import TestConfiguration from "../../config/TestConfiguration"
import * as fixtures from "../../fixtures"
describe("Internal API - Application creation", () => {
const config = new TestConfiguration()
beforeAll(async () => {
await config.beforeAll()
})
afterAll(async () => {
await config.afterAll()
})
it("Get applications without applications", async () => {
await config.api.apps.fetchEmptyAppList()
})
it("Get all Applications after creating an application", async () => {
await config.api.apps.create({
...fixtures.apps.generateApp(),
useTemplate: "false",
})
await config.api.apps.fetchAllApplications()
})
it("Get application details", async () => {
const app = await config.createApp({
...fixtures.apps.generateApp(),
useTemplate: "false",
})
const [appPackageResponse, appPackageJson] =
await config.api.apps.getAppPackage(app.appId!)
expect(appPackageJson.application.name).toEqual(app.name)
expect(appPackageJson.application.version).toEqual(app.version)
expect(appPackageJson.application.url).toEqual(app.url)
expect(appPackageJson.application.tenantId).toEqual(app.tenantId)
expect(appPackageJson.application.status).toEqual(app.status)
})
})

View File

@ -0,0 +1,19 @@
import TestConfiguration from "../../config/TestConfiguration"
describe("Internal API - Application creation, update, publish and delete", () => {
const config = new TestConfiguration()
beforeAll(async () => {
await config.beforeAll()
})
afterAll(async () => {
await config.afterAll()
})
it("DELETE - Delete an application", async () => {
const app = await config.createApp()
await config.api.apps.delete(app.appId!)
})
})

View File

@ -0,0 +1,54 @@
import TestConfiguration from "../../config/TestConfiguration"
import { db } from "@budibase/backend-core"
import * as fixtures from "../../fixtures"
describe("Internal API - Application creation, update, publish and delete", () => {
const config = new TestConfiguration()
beforeAll(async () => {
await config.beforeAll()
})
afterAll(async () => {
await config.afterAll()
})
it("Publish app", async () => {
// create the app
const app = await config.createApp(fixtures.apps.appFromTemplate())
// check preview renders
await config.api.apps.canRender()
// publish app
await config.api.apps.publish(app.appId)
// check published app renders
config.state.appId = db.getProdAppID(app.appId!)
await config.api.apps.canRender()
// unpublish app
await config.api.apps.unpublish(app.appId!)
})
it("Sync application before deployment", async () => {
const app = await config.createApp()
const [syncResponse, sync] = await config.api.apps.sync(app.appId!)
expect(sync).toEqual({
message: "App sync not required, app not deployed.",
})
})
it("Sync application after deployment", async () => {
const app = await config.createApp()
// publish app
await config.api.apps.publish(app._id)
const [syncResponse, sync] = await config.api.apps.sync(app.appId!)
expect(sync).toEqual({
message: "App sync completed successfully.",
})
})
})

View File

@ -0,0 +1,45 @@
import TestConfiguration from "../../config/TestConfiguration"
import { generator } from "../../../shared"
import * as fixtures from "../../fixtures"
describe("Internal API - Application creation, update, publish and delete", () => {
const config = new TestConfiguration()
beforeAll(async () => {
await config.beforeAll()
})
afterAll(async () => {
await config.afterAll()
})
it("Update an application", async () => {
const app = await config.createApp()
await config.api.apps.rename(app.appId!, app.name!, {
name: generator.word(),
})
})
it("Revert Changes without changes", async () => {
const app = await config.createApp()
await config.api.apps.revertUnpublished(app.appId!)
})
it("Revert Changes", async () => {
const app = await config.createApp()
// publish app
await config.api.apps.publish(app._id)
// Change/add component to the app
await config.api.screens.create(fixtures.screens.generateScreen("BASIC"))
// // Revert the app to published state
await config.api.apps.revertPublished(app.appId!)
// Check screen is removed
await config.api.apps.getRoutes()
})
})

View File

@ -0,0 +1,27 @@
import TestConfiguration from "../../config/TestConfiguration"
import * as fixtures from "../../fixtures"
describe("Internal API - Data Sources", () => {
const config = new TestConfiguration()
beforeAll(async () => {
await config.beforeAll()
})
afterAll(async () => {
await config.afterAll()
})
it("Create an app with a data source", async () => {
// Create app
await config.createApp()
// Create Screen
const roleArray = ["BASIC", "POWER", "ADMIN", "PUBLIC"]
for (let role in roleArray) {
const [response, screen] = await config.api.screens.create(
fixtures.screens.generateScreen(roleArray[role])
)
}
})
})

View File

@ -0,0 +1,51 @@
import TestConfiguration from "../../config/TestConfiguration"
import * as fixtures from "../../fixtures"
describe("Internal API - /screens endpoints", () => {
const config = new TestConfiguration()
beforeAll(async () => {
await config.beforeAll()
})
afterAll(async () => {
await config.afterAll()
})
it("Create a screen with each role type", async () => {
// Create app
await config.createApp()
// Create Screen
const roleArray = ["BASIC", "POWER", "ADMIN", "PUBLIC"]
for (let role in roleArray) {
const [response, screen] = await config.api.screens.create(
fixtures.screens.generateScreen(roleArray[role])
)
}
})
it("Get screens", async () => {
// Create app
await config.createApp()
// Create Screen
await config.api.screens.create(fixtures.screens.generateScreen("BASIC"))
// Check screen exists
await config.api.apps.getRoutes(true)
})
it("Delete a screen", async () => {
// Create app
await config.createApp()
// Create Screen
const [screenResponse, screen] = await config.api.screens.create(
fixtures.screens.generateScreen("BASIC")
)
// Delete Screen
await config.api.screens.delete(screen._id!, screen._rev!)
})
})

View File

@ -0,0 +1,144 @@
import TestConfiguration from "../../config/TestConfiguration"
import { generator } from "../../../shared"
import * as fixtures from "../../fixtures"
describe("Internal API - Table Operations", () => {
const config = new TestConfiguration()
beforeAll(async () => {
await config.beforeAll()
})
afterAll(async () => {
await config.afterAll()
})
async function createAppFromTemplate() {
return config.api.apps.create({
name: generator.word(),
url: `/${generator.word()}`,
useTemplate: "true",
templateName: "Near Miss Register",
templateKey: "app/near-miss-register",
templateFile: undefined,
})
}
it("Create and delete table, columns and rows", async () => {
// create the app
await config.createApp(fixtures.apps.appFromTemplate())
// Get current tables: expect 2 in this template
await config.api.tables.getAll(2)
// Add new table
const [createdTableResponse, createdTableData] =
await config.api.tables.save(fixtures.tables.generateTable())
//Table was added
await config.api.tables.getAll(3)
//Get information about the table
await config.api.tables.getTableById(createdTableData._id!)
//Add Column to table
const newColumn =
fixtures.tables.generateNewColumnForTable(createdTableData)
const [addColumnResponse, addColumnData] = await config.api.tables.save(
newColumn,
true
)
//Add Row to table
const newRow = fixtures.rows.generateNewRowForTable(addColumnData._id!)
await config.api.rows.add(addColumnData._id!, newRow)
//Get Row from table
const [getRowResponse, getRowData] = await config.api.rows.getAll(
addColumnData._id!
)
//Delete Row from table
const rowToDelete = {
rows: [getRowData[0]],
}
const [deleteRowResponse, deleteRowData] = await config.api.rows.delete(
addColumnData._id!,
rowToDelete
)
expect(deleteRowData[0]._id).toEqual(getRowData[0]._id)
//Delete the table
const [deleteTableResponse, deleteTable] = await config.api.tables.delete(
addColumnData._id!,
addColumnData._rev!
)
//Table was deleted
await config.api.tables.getAll(2)
})
it("Search and pagination", async () => {
// create the app
await config.createApp(fixtures.apps.appFromTemplate())
// Get current tables: expect 2 in this template
await config.api.tables.getAll(2)
// Add new table
const [createdTableResponse, createdTableData] =
await config.api.tables.save(fixtures.tables.generateTable())
//Table was added
await config.api.tables.getAll(3)
//Get information about the table
await config.api.tables.getTableById(createdTableData._id!)
//Add Column to table
const newColumn =
fixtures.tables.generateNewColumnForTable(createdTableData)
const [addColumnResponse, addColumnData] = await config.api.tables.save(
newColumn,
true
)
//Add Row to table
let newRow = fixtures.rows.generateNewRowForTable(addColumnData._id!)
await config.api.rows.add(addColumnData._id!, newRow)
//Search single row
await config.api.rows.searchNoPagination(
createdTableData._id!,
fixtures.rows.searchBody(createdTableData.primaryDisplay!)
)
//Add 10 more rows
for (let i = 0; i < 10; i++) {
let newRow = fixtures.rows.generateNewRowForTable(addColumnData._id!)
await config.api.rows.add(addColumnData._id!, newRow)
}
//Search rows with pagination
const [allRowsResponse, allRowsJson] =
await config.api.rows.searchWithPagination(
createdTableData._id!,
fixtures.rows.searchBody(createdTableData.primaryDisplay!)
)
//Delete Rows from table
const rowToDelete = {
rows: [allRowsJson],
}
const [deleteRowResponse, deleteRowData] = await config.api.rows.delete(
createdTableData._id!,
rowToDelete
)
//Search single row
await config.api.rows.searchWithPagination(
createdTableData._id!,
fixtures.rows.searchBody(createdTableData.primaryDisplay!)
)
})
})

View File

@ -0,0 +1,107 @@
import TestConfiguration from "../../config/TestConfiguration"
import { User } from "@budibase/types"
import { db } from "@budibase/backend-core"
import * as fixtures from "../../fixtures"
describe("Internal API - App Specific Roles & Permissions", () => {
const config = new TestConfiguration()
// Before each test, login as admin. Some tests will require login as a different user
beforeAll(async () => {
await config.beforeAll()
})
afterAll(async () => {
await config.afterAll()
})
afterAll(async () => {
await config.afterAll()
})
it("Add BASIC user to app", async () => {
const appUser = fixtures.users.generateUser()
expect(appUser[0].builder?.global).toEqual(false)
expect(appUser[0].admin?.global).toEqual(false)
const [createUserResponse, createUserJson] =
await config.api.users.addMultiple(appUser)
const app = await config.createApp(fixtures.apps.appFromTemplate())
const [userInfoResponse, userInfoJson] = await config.api.users.getInfo(
createUserJson.created.successful[0]._id
)
const body: User = {
...userInfoJson,
roles: {
[app.appId!]: "BASIC",
},
}
await config.api.users.updateInfo(body)
const [changedUserInfoResponse, changedUserInfoJson] =
await config.api.users.getInfo(createUserJson.created.successful[0]._id)
expect(changedUserInfoJson.roles[app.appId!]).toBeDefined()
expect(changedUserInfoJson.roles[app.appId!]).toEqual("BASIC")
})
it("Add ADMIN user to app", async () => {
// Create a user with ADMIN role and check if it was created successfully
const adminUser = fixtures.users.generateUser(1, "admin")
expect(adminUser[0].builder?.global).toEqual(true)
expect(adminUser[0].admin?.global).toEqual(true)
const [createUserResponse, createUserJson] =
await config.api.users.addMultiple(adminUser)
// const app = await config.createApp(fixtures.apps.appFromTemplate())
const app = await config.createApp(fixtures.apps.appFromTemplate())
const [userInfoResponse, userInfoJson] = await config.api.users.getInfo(
createUserJson.created.successful[0]._id
)
const body: User = {
...userInfoJson,
roles: {
[app.appId!]: "ADMIN",
},
}
await config.api.users.updateInfo(body)
const [changedUserInfoResponse, changedUserInfoJson] =
await config.api.users.getInfo(createUserJson.created.successful[0]._id)
expect(changedUserInfoJson.roles[app.appId!]).toBeDefined()
expect(changedUserInfoJson.roles[app.appId!]).toEqual("ADMIN")
// publish app
await config.api.apps.publish(app.appId)
// check published app renders
config.state.appId = db.getProdAppID(app.appId!)
await config.api.apps.canRender()
})
it("Add POWER user to app", async () => {
const powerUser = fixtures.users.generateUser(1, "developer")
expect(powerUser[0].builder?.global).toEqual(true)
const [createUserResponse, createUserJson] =
await config.api.users.addMultiple(powerUser)
const app = await config.createApp()
const [userInfoResponse, userInfoJson] = await config.api.users.getInfo(
createUserJson.created.successful[0]._id
)
const body: User = {
...userInfoJson,
roles: {
[app.appId!]: "POWER",
},
}
await config.api.users.updateInfo(body)
// Get the user information again and check if the role was added
const [changedUserInfoResponse, changedUserInfoJson] =
await config.api.users.getInfo(createUserJson.created.successful[0]._id)
expect(changedUserInfoJson.roles[app.appId!]).toBeDefined()
expect(changedUserInfoJson.roles[app.appId!]).toEqual("POWER")
})
})

View File

@ -1,27 +1,16 @@
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 } from "../../../config/internal-api/fixtures/applications"
import { generateUser } from "../../../config/internal-api/fixtures/userManagement"
import TestConfiguration from "../../config/TestConfiguration"
import { App, User } from "@budibase/types"
import generateScreen from "../../../config/internal-api/fixtures/screens"
import { db } from "@budibase/backend-core"
import * as fixtures from "./../../fixtures"
describe.skip("Internal API - App Specific Roles & Permissions", () => {
let api: InternalAPIClient
let accountsAPI: AccountsAPIClient
let config: TestConfiguration<Application>
const config = new TestConfiguration()
let app: Partial<App>
// 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<Application>(api, accountsAPI)
await config.setupAccountAndTenant()
app = await config.applications.create(generateApp())
config.applications.api.appId = app.appId
await config.beforeAll()
app = await config.createApp()
})
afterAll(async () => {
@ -30,12 +19,11 @@ describe.skip("Internal API - App Specific Roles & Permissions", () => {
it("Custom role access for level 1 permissions", async () => {
// Set up user
const appUser = generateUser()
const appUser = fixtures.users.generateUser()
expect(appUser[0].builder?.global).toEqual(false)
expect(appUser[0].admin?.global).toEqual(false)
const [createUserResponse, createUserJson] = await config.users.addMultiple(
appUser
)
const [createUserResponse, createUserJson] =
await config.api.users.addMultiple(appUser)
//Create level 1 role
const role = {
@ -43,12 +31,11 @@ describe.skip("Internal API - App Specific Roles & Permissions", () => {
permissionId: "public",
name: "level 1",
}
const [createRoleResponse, createRoleJson] = await config.users.createRole(
role
)
const [createRoleResponse, createRoleJson] =
await config.api.users.createRole(role)
// Update user roles
const [userInfoResponse, userInfoJson] = await config.users.getInfo(
const [userInfoResponse, userInfoJson] = await config.api.users.getInfo(
createUserJson.created.successful[0]._id
)
const prodAppId = db.getProdAppID(app.appId!)
@ -60,42 +47,45 @@ describe.skip("Internal API - App Specific Roles & Permissions", () => {
[prodAppId]: createRoleJson._id,
},
}
await config.users.updateInfo(body)
await config.api.users.updateInfo(body)
const [changedUserInfoResponse, changedUserInfoJson] =
await config.users.getInfo(createUserJson.created.successful[0]._id)
await config.api.users.getInfo(createUserJson.created.successful[0]._id)
expect(changedUserInfoJson.roles[prodAppId]).toBeDefined()
expect(changedUserInfoJson.roles[prodAppId]).toEqual(createRoleJson._id)
await config.screen.create(generateScreen("BASIC"))
await config.screen.create(generateScreen("POWER"))
await config.screen.create(generateScreen("ADMIN"))
await config.api.screens.create(fixtures.screens.generateScreen("BASIC"))
await config.api.screens.create(fixtures.screens.generateScreen("POWER"))
await config.api.screens.create(fixtures.screens.generateScreen("ADMIN"))
await config.applications.publish(<string>app.appId)
await config.api.apps.publish(app.appId)
const [firstappPackageResponse, firstappPackageJson] =
await config.applications.getAppPackage(<string>app.appId)
await config.api.apps.getAppPackage(app.appId!)
expect(firstappPackageJson.screens).toBeDefined()
expect(firstappPackageJson.screens.length).toEqual(3)
// login with level 1 user
await config.login(appUser[0].email!, appUser[0].password!)
const [selfInfoResponse, selfInfoJson] = await config.users.getSelf()
await config.login(
config.state.tenantId!,
appUser[0].email!,
appUser[0].password!
)
const [selfInfoResponse, selfInfoJson] = await config.api.users.getSelf()
// fetch app package
const [appPackageResponse, appPackageJson] =
await config.applications.getAppPackage(app.appId!)
await config.api.apps.getAppPackage(app.appId!)
expect(appPackageJson.screens).toBeDefined()
expect(appPackageJson.screens.length).toEqual(1)
})
it("Custom role access for level 2 permissions", async () => {
// Set up user
const appUser = generateUser()
const appUser = fixtures.users.generateUser()
expect(appUser[0].builder?.global).toEqual(false)
expect(appUser[0].admin?.global).toEqual(false)
const [createUserResponse, createUserJson] = await config.users.addMultiple(
appUser
)
const [createUserResponse, createUserJson] =
await config.api.users.addMultiple(appUser)
// Create App
@ -105,12 +95,11 @@ describe.skip("Internal API - App Specific Roles & Permissions", () => {
permissionId: "read_only",
name: "level 2",
}
const [createRoleResponse, createRoleJson] = await config.users.createRole(
role
)
const [createRoleResponse, createRoleJson] =
await config.api.users.createRole(role)
// Update user roles
const [userInfoResponse, userInfoJson] = await config.users.getInfo(
const [userInfoResponse, userInfoJson] = await config.api.users.getInfo(
createUserJson.created.successful[0]._id
)
const prodAppId = db.getProdAppID(app.appId!)
@ -122,40 +111,39 @@ describe.skip("Internal API - App Specific Roles & Permissions", () => {
[prodAppId]: createRoleJson._id,
},
}
await config.users.updateInfo(body)
await config.api.users.updateInfo(body)
const [changedUserInfoResponse, changedUserInfoJson] =
await config.users.getInfo(createUserJson.created.successful[0]._id)
await config.api.users.getInfo(createUserJson.created.successful[0]._id)
expect(changedUserInfoJson.roles[prodAppId]).toBeDefined()
expect(changedUserInfoJson.roles[prodAppId]).toEqual(createRoleJson._id)
await config.screen.create(generateScreen("BASIC"))
await config.screen.create(generateScreen("POWER"))
await config.screen.create(generateScreen("ADMIN"))
await config.api.screens.create(fixtures.screens.generateScreen("BASIC"))
await config.api.screens.create(fixtures.screens.generateScreen("POWER"))
await config.api.screens.create(fixtures.screens.generateScreen("ADMIN"))
await config.applications.publish(<string>app.appId)
await config.api.apps.publish(app.appId)
const [firstappPackageResponse, firstappPackageJson] =
await config.applications.getAppPackage(<string>app.appId)
await config.api.apps.getAppPackage(app.appId!)
expect(firstappPackageJson.screens).toBeDefined()
expect(firstappPackageJson.screens.length).toEqual(3)
// login with level 1 user
await config.login(appUser[0].email!, appUser[0].password!)
const [selfInfoResponse, selfInfoJson] = await config.users.getSelf()
const [selfInfoResponse, selfInfoJson] = await config.api.users.getSelf()
// fetch app package
const [appPackageResponse, appPackageJson] =
await config.applications.getAppPackage(app.appId!)
await config.api.apps.getAppPackage(app.appId!)
expect(appPackageJson.screens).toBeDefined()
expect(appPackageJson.screens.length).toEqual(1)
})
it("Custom role access for level 3 permissions", async () => {
const appUser = generateUser()
const appUser = fixtures.users.generateUser()
expect(appUser[0].builder?.global).toEqual(false)
expect(appUser[0].admin?.global).toEqual(false)
const [createUserResponse, createUserJson] = await config.users.addMultiple(
appUser
)
const [createUserResponse, createUserJson] =
await config.api.users.addMultiple(appUser)
// Create App
@ -165,12 +153,11 @@ describe.skip("Internal API - App Specific Roles & Permissions", () => {
permissionId: "write",
name: "level 3",
}
const [createRoleResponse, createRoleJson] = await config.users.createRole(
role
)
const [createRoleResponse, createRoleJson] =
await config.api.users.createRole(role)
// Update user roles
const [userInfoResponse, userInfoJson] = await config.users.getInfo(
const [userInfoResponse, userInfoJson] = await config.api.users.getInfo(
createUserJson.created.successful[0]._id
)
const prodAppId = db.getProdAppID(app.appId!)
@ -182,40 +169,39 @@ describe.skip("Internal API - App Specific Roles & Permissions", () => {
[prodAppId]: createRoleJson._id,
},
}
await config.users.updateInfo(body)
await config.api.users.updateInfo(body)
const [changedUserInfoResponse, changedUserInfoJson] =
await config.users.getInfo(createUserJson.created.successful[0]._id)
await config.api.users.getInfo(createUserJson.created.successful[0]._id)
expect(changedUserInfoJson.roles[prodAppId]).toBeDefined()
expect(changedUserInfoJson.roles[prodAppId]).toEqual(createRoleJson._id)
await config.screen.create(generateScreen("BASIC"))
await config.screen.create(generateScreen("POWER"))
await config.screen.create(generateScreen("ADMIN"))
await config.api.screens.create(fixtures.screens.generateScreen("BASIC"))
await config.api.screens.create(fixtures.screens.generateScreen("POWER"))
await config.api.screens.create(fixtures.screens.generateScreen("ADMIN"))
await config.applications.publish(<string>app.appId)
await config.api.apps.publish(app.appId)
const [firstappPackageResponse, firstappPackageJson] =
await config.applications.getAppPackage(<string>app.appId)
await config.api.apps.getAppPackage(app.appId!)
expect(firstappPackageJson.screens).toBeDefined()
expect(firstappPackageJson.screens.length).toEqual(3)
// login with level 1 user
await config.login(appUser[0].email!, appUser[0].password!)
const [selfInfoResponse, selfInfoJson] = await config.users.getSelf()
const [selfInfoResponse, selfInfoJson] = await config.api.users.getSelf()
// fetch app package
const [appPackageResponse, appPackageJson] =
await config.applications.getAppPackage(app.appId!)
await config.api.apps.getAppPackage(app.appId!)
expect(appPackageJson.screens).toBeDefined()
expect(appPackageJson.screens.length).toEqual(1)
})
it("Custom role access for level 4 permissions", async () => {
const appUser = generateUser()
const appUser = fixtures.users.generateUser()
expect(appUser[0].builder?.global).toEqual(false)
expect(appUser[0].admin?.global).toEqual(false)
const [createUserResponse, createUserJson] = await config.users.addMultiple(
appUser
)
const [createUserResponse, createUserJson] =
await config.api.users.addMultiple(appUser)
// Create App
@ -225,12 +211,11 @@ describe.skip("Internal API - App Specific Roles & Permissions", () => {
permissionId: "power",
name: "level 4",
}
const [createRoleResponse, createRoleJson] = await config.users.createRole(
role
)
const [createRoleResponse, createRoleJson] =
await config.api.users.createRole(role)
// Update user roles
const [userInfoResponse, userInfoJson] = await config.users.getInfo(
const [userInfoResponse, userInfoJson] = await config.api.users.getInfo(
createUserJson.created.successful[0]._id
)
const prodAppId = db.getProdAppID(app.appId!)
@ -242,40 +227,39 @@ describe.skip("Internal API - App Specific Roles & Permissions", () => {
[prodAppId]: createRoleJson._id,
},
}
await config.users.updateInfo(body)
await config.api.users.updateInfo(body)
const [changedUserInfoResponse, changedUserInfoJson] =
await config.users.getInfo(createUserJson.created.successful[0]._id)
await config.api.users.getInfo(createUserJson.created.successful[0]._id)
expect(changedUserInfoJson.roles[prodAppId]).toBeDefined()
expect(changedUserInfoJson.roles[prodAppId]).toEqual(createRoleJson._id)
await config.screen.create(generateScreen("BASIC"))
await config.screen.create(generateScreen("POWER"))
await config.screen.create(generateScreen("ADMIN"))
await config.api.screens.create(fixtures.screens.generateScreen("BASIC"))
await config.api.screens.create(fixtures.screens.generateScreen("POWER"))
await config.api.screens.create(fixtures.screens.generateScreen("ADMIN"))
await config.applications.publish(<string>app.appId)
await config.api.apps.publish(app.appId)
const [firstappPackageResponse, firstappPackageJson] =
await config.applications.getAppPackage(<string>app.appId)
await config.api.apps.getAppPackage(app.appId!)
expect(firstappPackageJson.screens).toBeDefined()
expect(firstappPackageJson.screens.length).toEqual(3)
// login with level 1 user
await config.login(appUser[0].email!, appUser[0].password!)
const [selfInfoResponse, selfInfoJson] = await config.users.getSelf()
const [selfInfoResponse, selfInfoJson] = await config.api.users.getSelf()
// fetch app package
const [appPackageResponse, appPackageJson] =
await config.applications.getAppPackage(app.appId!)
await config.api.apps.getAppPackage(app.appId!)
expect(appPackageJson.screens).toBeDefined()
expect(appPackageJson.screens.length).toEqual(1)
})
it("Custom role access for level 5 permissions", async () => {
const appUser = generateUser()
const appUser = fixtures.users.generateUser()
expect(appUser[0].builder?.global).toEqual(false)
expect(appUser[0].admin?.global).toEqual(false)
const [createUserResponse, createUserJson] = await config.users.addMultiple(
appUser
)
const [createUserResponse, createUserJson] =
await config.api.users.addMultiple(appUser)
// Create App
@ -285,12 +269,11 @@ describe.skip("Internal API - App Specific Roles & Permissions", () => {
permissionId: "admin",
name: "level 5",
}
const [createRoleResponse, createRoleJson] = await config.users.createRole(
role
)
const [createRoleResponse, createRoleJson] =
await config.api.users.createRole(role)
// Update user roles
const [userInfoResponse, userInfoJson] = await config.users.getInfo(
const [userInfoResponse, userInfoJson] = await config.api.users.getInfo(
createUserJson.created.successful[0]._id
)
const prodAppId = db.getProdAppID(app.appId!)
@ -302,30 +285,30 @@ describe.skip("Internal API - App Specific Roles & Permissions", () => {
[prodAppId]: createRoleJson._id,
},
}
await config.users.updateInfo(body)
await config.api.users.updateInfo(body)
const [changedUserInfoResponse, changedUserInfoJson] =
await config.users.getInfo(createUserJson.created.successful[0]._id)
await config.api.users.getInfo(createUserJson.created.successful[0]._id)
expect(changedUserInfoJson.roles[prodAppId]).toBeDefined()
expect(changedUserInfoJson.roles[prodAppId]).toEqual(createRoleJson._id)
await config.screen.create(generateScreen("BASIC"))
await config.screen.create(generateScreen("POWER"))
await config.screen.create(generateScreen("ADMIN"))
await config.api.screens.create(fixtures.screens.generateScreen("BASIC"))
await config.api.screens.create(fixtures.screens.generateScreen("POWER"))
await config.api.screens.create(fixtures.screens.generateScreen("ADMIN"))
await config.applications.publish(<string>app.appId)
await config.api.apps.publish(app.appId)
const [firstappPackageResponse, firstappPackageJson] =
await config.applications.getAppPackage(<string>app.appId)
await config.api.apps.getAppPackage(app.appId!)
expect(firstappPackageJson.screens).toBeDefined()
expect(firstappPackageJson.screens.length).toEqual(3)
// login with level 1 user
await config.login(appUser[0].email!, appUser[0].password!)
const [selfInfoResponse, selfInfoJson] = await config.users.getSelf()
const [selfInfoResponse, selfInfoJson] = await config.api.users.getSelf()
// fetch app package
const [appPackageResponse, appPackageJson] =
await config.applications.getAppPackage(app.appId!)
await config.api.apps.getAppPackage(app.appId!)
expect(appPackageJson.screens).toBeDefined()
expect(appPackageJson.screens.length).toEqual(1)
})

View File

@ -1,24 +1,14 @@
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 } from "../../../config/internal-api/fixtures/applications"
import { generateUser } from "../../../config/internal-api/fixtures/userManagement"
import TestConfiguration from "../../config/TestConfiguration"
import { User } from "@budibase/types"
import generateScreen from "../../../config/internal-api/fixtures/screens"
import { db } from "@budibase/backend-core"
import * as fixtures from "./../../fixtures"
describe.skip("Internal API - Role screen access", () => {
let api: InternalAPIClient
let accountsAPI: AccountsAPIClient
let config: TestConfiguration<Application>
const config = new 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<Application>(api, accountsAPI)
await config.setupAccountAndTenant()
await config.beforeAll()
})
afterAll(async () => {
@ -27,19 +17,17 @@ describe.skip("Internal API - Role screen access", () => {
it("Check Screen access for BASIC Role", async () => {
// Set up user
const appUser = generateUser()
const appUser = fixtures.users.generateUser()
expect(appUser[0].builder?.global).toEqual(false)
expect(appUser[0].admin?.global).toEqual(false)
const [createUserResponse, createUserJson] = await config.users.addMultiple(
appUser
)
const [createUserResponse, createUserJson] =
await config.api.users.addMultiple(appUser)
// Create App
const app = await config.applications.create(generateApp())
config.applications.api.appId = app.appId
const app = await config.createApp()
// Update user roles
const [userInfoResponse, userInfoJson] = await config.users.getInfo(
const [userInfoResponse, userInfoJson] = await config.api.users.getInfo(
createUserJson.created.successful[0]._id
)
const prodAppId = db.getProdAppID(app.appId!)
@ -51,31 +39,31 @@ describe.skip("Internal API - Role screen access", () => {
[prodAppId]: "BASIC",
},
}
await config.users.updateInfo(body)
await config.api.users.updateInfo(body)
const [changedUserInfoResponse, changedUserInfoJson] =
await config.users.getInfo(createUserJson.created.successful[0]._id)
await config.api.users.getInfo(createUserJson.created.successful[0]._id)
expect(changedUserInfoJson.roles[prodAppId]).toBeDefined()
expect(changedUserInfoJson.roles[prodAppId]).toEqual("BASIC")
await config.screen.create(generateScreen("BASIC"))
await config.screen.create(generateScreen("POWER"))
await config.screen.create(generateScreen("ADMIN"))
await config.api.screens.create(fixtures.screens.generateScreen("BASIC"))
await config.api.screens.create(fixtures.screens.generateScreen("POWER"))
await config.api.screens.create(fixtures.screens.generateScreen("ADMIN"))
await config.applications.publish(<string>app.appId)
await config.api.apps.publish(app.appId)
const [firstappPackageResponse, firstappPackageJson] =
await config.applications.getAppPackage(<string>app.appId)
await config.api.apps.getAppPackage(app.appId!)
expect(firstappPackageJson.screens).toBeDefined()
expect(firstappPackageJson.screens.length).toEqual(3)
// login with BASIC user
await config.login(appUser[0].email!, appUser[0].password!)
const [selfInfoResponse, selfInfoJson] = await config.users.getSelf()
const [selfInfoResponse, selfInfoJson] = await config.api.users.getSelf()
// fetch app package
const [appPackageResponse, appPackageJson] =
await config.applications.getAppPackage(app.appId!)
await config.api.apps.getAppPackage(app.appId!)
expect(appPackageJson.screens).toBeDefined()
expect(appPackageJson.screens.length).toEqual(1)
expect(appPackageJson.screens[0].routing.roleId).toEqual("BASIC")
@ -83,19 +71,17 @@ describe.skip("Internal API - Role screen access", () => {
it("Check Screen access for POWER role", async () => {
// Set up user
const appUser = generateUser()
const appUser = fixtures.users.generateUser()
expect(appUser[0].builder?.global).toEqual(false)
expect(appUser[0].admin?.global).toEqual(false)
const [createUserResponse, createUserJson] = await config.users.addMultiple(
appUser
)
const [createUserResponse, createUserJson] =
await config.api.users.addMultiple(appUser)
// Create App
const app = await config.applications.create(generateApp())
config.applications.api.appId = app.appId
const app = await config.createApp()
// Update user roles
const [userInfoResponse, userInfoJson] = await config.users.getInfo(
const [userInfoResponse, userInfoJson] = await config.api.users.getInfo(
createUserJson.created.successful[0]._id
)
const prodAppId = db.getProdAppID(app.appId!)
@ -107,49 +93,47 @@ describe.skip("Internal API - Role screen access", () => {
[prodAppId]: "POWER",
},
}
await config.users.updateInfo(body)
await config.api.users.updateInfo(body)
const [changedUserInfoResponse, changedUserInfoJson] =
await config.users.getInfo(createUserJson.created.successful[0]._id)
await config.api.users.getInfo(createUserJson.created.successful[0]._id)
expect(changedUserInfoJson.roles[prodAppId]).toBeDefined()
expect(changedUserInfoJson.roles[prodAppId]).toEqual("POWER")
await config.screen.create(generateScreen("BASIC"))
await config.screen.create(generateScreen("POWER"))
await config.screen.create(generateScreen("ADMIN"))
await config.api.screens.create(fixtures.screens.generateScreen("BASIC"))
await config.api.screens.create(fixtures.screens.generateScreen("POWER"))
await config.api.screens.create(fixtures.screens.generateScreen("ADMIN"))
await config.applications.publish(<string>app.appId)
await config.api.apps.publish(app.appId)
const [firstappPackageResponse, firstappPackageJson] =
await config.applications.getAppPackage(<string>app.appId)
await config.api.apps.getAppPackage(app.appId!)
expect(firstappPackageJson.screens).toBeDefined()
expect(firstappPackageJson.screens.length).toEqual(3)
// login with POWER user
await config.login(appUser[0].email!, appUser[0].password!)
const [selfInfoResponse, selfInfoJson] = await config.users.getSelf()
const [selfInfoResponse, selfInfoJson] = await config.api.users.getSelf()
// fetch app package
const [appPackageResponse, appPackageJson] =
await config.applications.getAppPackage(app.appId!)
await config.api.apps.getAppPackage(app.appId!)
expect(appPackageJson.screens).toBeDefined()
expect(appPackageJson.screens.length).toEqual(2)
})
it("Check Screen access for ADMIN role", async () => {
// Set up user
const appUser = generateUser()
const appUser = fixtures.users.generateUser()
expect(appUser[0].builder?.global).toEqual(false)
expect(appUser[0].admin?.global).toEqual(false)
const [createUserResponse, createUserJson] = await config.users.addMultiple(
appUser
)
const [createUserResponse, createUserJson] =
await config.api.users.addMultiple(appUser)
// Create App
const app = await config.applications.create(generateApp())
config.applications.api.appId = app.appId
const app = await config.createApp()
// Update user roles
const [userInfoResponse, userInfoJson] = await config.users.getInfo(
const [userInfoResponse, userInfoJson] = await config.api.users.getInfo(
createUserJson.created.successful[0]._id
)
const prodAppId = db.getProdAppID(app.appId!)
@ -161,30 +145,30 @@ describe.skip("Internal API - Role screen access", () => {
[prodAppId]: "ADMIN",
},
}
await config.users.updateInfo(body)
await config.api.users.updateInfo(body)
const [changedUserInfoResponse, changedUserInfoJson] =
await config.users.getInfo(createUserJson.created.successful[0]._id)
await config.api.users.getInfo(createUserJson.created.successful[0]._id)
expect(changedUserInfoJson.roles[prodAppId]).toBeDefined()
expect(changedUserInfoJson.roles[prodAppId]).toEqual("ADMIN")
await config.screen.create(generateScreen("BASIC"))
await config.screen.create(generateScreen("POWER"))
await config.screen.create(generateScreen("ADMIN"))
await config.api.screens.create(fixtures.screens.generateScreen("BASIC"))
await config.api.screens.create(fixtures.screens.generateScreen("POWER"))
await config.api.screens.create(fixtures.screens.generateScreen("ADMIN"))
await config.applications.publish(<string>app.appId)
await config.api.apps.publish(app.appId)
const [firstappPackageResponse, firstappPackageJson] =
await config.applications.getAppPackage(<string>app.appId)
await config.api.apps.getAppPackage(app.appId!)
expect(firstappPackageJson.screens).toBeDefined()
expect(firstappPackageJson.screens.length).toEqual(3)
// login with ADMIN user
await config.login(appUser[0].email!, appUser[0].password!)
const [selfInfoResponse, selfInfoJson] = await config.users.getSelf()
const [selfInfoResponse, selfInfoJson] = await config.api.users.getSelf()
// fetch app package
const [appPackageResponse, appPackageJson] =
await config.applications.getAppPackage(app.appId!)
await config.api.apps.getAppPackage(app.appId!)
expect(appPackageJson.screens).toBeDefined()
expect(appPackageJson.screens.length).toEqual(3)
})

View File

@ -0,0 +1,123 @@
import TestConfiguration from "../../config/TestConfiguration"
import { User } from "@budibase/types"
import * as fixtures from "./../../fixtures"
describe.skip("Internal API - Role table access", () => {
const config = new TestConfiguration()
// Before each test, login as admin. Some tests will require login as a different user
beforeEach(async () => {
await config.beforeAll()
})
afterAll(async () => {
await config.afterAll()
})
it("Check Table access for app user", async () => {
const appUser = fixtures.users.generateUser()
expect(appUser[0].builder?.global).toEqual(false)
expect(appUser[0].admin?.global).toEqual(false)
const [createUserResponse, createUserJson] =
await config.api.users.addMultiple(appUser)
const app = await config.createApp()
const [userInfoResponse, userInfoJson] = await config.api.users.getInfo(
createUserJson.created.successful[0]._id
)
const body: User = {
...userInfoJson,
roles: {
[app.appId!]: "BASIC",
},
}
await config.api.users.updateInfo(body)
const [changedUserInfoResponse, changedUserInfoJson] =
await config.api.users.getInfo(createUserJson.created.successful[0]._id)
expect(changedUserInfoJson.roles[app.appId!]).toBeDefined()
expect(changedUserInfoJson.roles[app.appId!]).toEqual("BASIC")
const [createdTableResponse, createdTableData] =
await config.api.tables.save(fixtures.tables.generateTable())
await config.login(appUser[0].email!, appUser[0].password!)
const newColumn =
fixtures.tables.generateNewColumnForTable(createdTableData)
await config.api.tables.forbiddenSave(newColumn)
await config.api.tables.forbiddenSave(fixtures.tables.generateTable())
})
it.skip("Check Table access for developer", async () => {
const developer = fixtures.users.generateUser(1, "developer")
expect(developer[0].builder?.global).toEqual(true)
const [createUserResponse, createUserJson] =
await config.api.users.addMultiple(developer)
const app = await config.createApp()
const [userInfoResponse, userInfoJson] = await config.api.users.getInfo(
createUserJson.created.successful[0]._id
)
const body: User = {
...userInfoJson,
roles: {
[app.appId!]: "POWER",
},
}
await config.api.users.updateInfo(body)
const [changedUserInfoResponse, changedUserInfoJson] =
await config.api.users.getInfo(createUserJson.created.successful[0]._id)
expect(changedUserInfoJson.roles[app.appId!]).toBeDefined()
expect(changedUserInfoJson.roles[app.appId!]).toEqual("POWER")
const [createdTableResponse, createdTableData] =
await config.api.tables.save(fixtures.tables.generateTable())
await config.login(developer[0].email!, developer[0].password!)
const newColumn =
fixtures.tables.generateNewColumnForTable(createdTableData)
const [addColumnResponse, addColumnData] = await config.api.tables.save(
newColumn,
true
)
})
it.skip("Check Table access for admin", async () => {
const adminUser = fixtures.users.generateUser(1, "admin")
expect(adminUser[0].builder?.global).toEqual(true)
expect(adminUser[0].admin?.global).toEqual(true)
const [createUserResponse, createUserJson] =
await config.api.users.addMultiple(adminUser)
const app = await config.createApp()
const [userInfoResponse, userInfoJson] = await config.api.users.getInfo(
createUserJson.created.successful[0]._id
)
const body: User = {
...userInfoJson,
roles: {
[app.appId!]: "ADMIN",
},
}
await config.api.users.updateInfo(body)
const [changedUserInfoResponse, changedUserInfoJson] =
await config.api.users.getInfo(createUserJson.created.successful[0]._id)
expect(changedUserInfoJson.roles[app.appId!]).toBeDefined()
expect(changedUserInfoJson.roles[app.appId!]).toEqual("ADMIN")
await config.login(adminUser[0].email!, adminUser[0].password!)
const [createdTableResponse, createdTableData] =
await config.api.tables.save(fixtures.tables.generateTable())
const newColumn =
fixtures.tables.generateNewColumnForTable(createdTableData)
const [addColumnResponse, addColumnData] = await config.api.tables.save(
newColumn,
true
)
})
})

View File

@ -1,18 +1,13 @@
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 TestConfiguration from "../../config/TestConfiguration"
import { User } from "@budibase/types"
import * as fixtures from "./../../fixtures"
describe("Internal API - User Management & Permissions", () => {
const api = new InternalAPIClient()
const accountsAPI = new AccountsAPIClient()
const config = new TestConfiguration<Application>(api, accountsAPI)
const config = new TestConfiguration()
// Before each test, login as admin. Some tests will require login as a different user
beforeAll(async () => {
await config.setupAccountAndTenant()
await config.beforeAll()
})
afterAll(async () => {
@ -21,60 +16,60 @@ describe("Internal API - User Management & Permissions", () => {
it("Add Users with different roles", async () => {
// Get all users
await config.users.search()
await config.api.users.search()
// Get all roles
await config.users.getRoles()
await config.api.users.getRoles()
// Add users with each role
const admin = generateUser(1, "admin")
const admin = fixtures.users.generateUser(1, "admin")
expect(admin[0].builder?.global).toEqual(true)
expect(admin[0].admin?.global).toEqual(true)
const developer = generateUser(1, "developer")
const developer = fixtures.users.generateUser(1, "developer")
expect(developer[0].builder?.global).toEqual(true)
const appUser = generateUser(1, "appUser")
const appUser = fixtures.users.generateUser(1, "appUser")
expect(appUser[0].builder?.global).toEqual(false)
expect(appUser[0].admin?.global).toEqual(false)
const userList = [...admin, ...developer, ...appUser]
await config.users.addMultiple(userList)
await config.api.users.addMultiple(userList)
// Check users are added
const [allUsersResponse, allUsersJson] = await config.users.getAll()
const [allUsersResponse, allUsersJson] = await config.api.users.getAll()
expect(allUsersJson.length).toBeGreaterThan(0)
})
it("Delete User", async () => {
const appUser = generateUser()
const appUser = fixtures.users.generateUser()
expect(appUser[0].builder?.global).toEqual(false)
expect(appUser[0].admin?.global).toEqual(false)
const [userResponse, userJson] = await config.users.addMultiple(appUser)
const [userResponse, userJson] = await config.api.users.addMultiple(appUser)
const userId = userJson.created.successful[0]._id
await config.users.delete(<string>userId)
await config.api.users.delete(userId)
})
it("Reset Password", async () => {
const appUser = generateUser()
const appUser = fixtures.users.generateUser()
expect(appUser[0].builder?.global).toEqual(false)
expect(appUser[0].admin?.global).toEqual(false)
const [userResponse, userJson] = await config.users.addMultiple(appUser)
const [userInfoResponse, userInfoJson] = await config.users.getInfo(
const [userResponse, userJson] = await config.api.users.addMultiple(appUser)
const [userInfoResponse, userInfoJson] = await config.api.users.getInfo(
userJson.created.successful[0]._id
)
const body: User = {
...userInfoJson,
password: "newPassword",
}
await config.users.forcePasswordReset(body)
await config.api.users.forcePasswordReset(body)
})
it("Change User information", async () => {
const appUser = generateUser()
const appUser = fixtures.users.generateUser()
expect(appUser[0].builder?.global).toEqual(false)
expect(appUser[0].admin?.global).toEqual(false)
const [userResponse, userJson] = await config.users.addMultiple(appUser)
const [userInfoResponse, userInfoJson] = await config.users.getInfo(
const [userResponse, userJson] = await config.api.users.addMultiple(appUser)
const [userInfoResponse, userInfoJson] = await config.api.users.getInfo(
userJson.created.successful[0]._id
)
const body: User = {
@ -85,10 +80,10 @@ describe("Internal API - User Management & Permissions", () => {
global: true,
},
}
await config.users.updateInfo(body)
await config.api.users.updateInfo(body)
const [changedUserInfoResponse, changedUserInfoJson] =
await config.users.getInfo(userJson.created.successful[0]._id)
await config.api.users.getInfo(userJson.created.successful[0]._id)
expect(changedUserInfoJson.builder?.global).toBeDefined()
expect(changedUserInfoJson.builder?.global).toEqual(true)
})

View File

@ -0,0 +1,78 @@
import { AccountInternalAPI } from "../account-api"
import * as fixtures from "../internal-api/fixtures"
import { BudibaseInternalAPI } from "../internal-api"
import { DEFAULT_TENANT_ID, logging } from "@budibase/backend-core"
import { CreateAccountRequest } from "@budibase/types"
import env from "../environment"
import { APIRequestOpts } from "../types"
// turn off or on context logging i.e. tenantId, appId etc
// it's not applicable for the qa run
logging.LOG_CONTEXT = false
const accountsApi = new AccountInternalAPI({})
const internalApi = new BudibaseInternalAPI({})
const API_OPTS: APIRequestOpts = { doExpect: false }
// @ts-ignore
global.qa = {}
async function createAccount() {
const account = fixtures.accounts.generateAccount()
await accountsApi.accounts.validateEmail(account.email, API_OPTS)
await accountsApi.accounts.validateTenantId(account.tenantId, API_OPTS)
await accountsApi.accounts.create(account, API_OPTS)
return account
}
async function loginAsAdmin() {
const username = env.BB_ADMIN_USER_EMAIL!
const password = env.BB_ADMIN_USER_PASSWORD!
const tenantId = DEFAULT_TENANT_ID
const [res, cookie] = await internalApi.auth.login(
tenantId,
username,
password,
API_OPTS
)
// @ts-ignore
global.qa.authCookie = cookie
}
async function loginAsAccount(account: CreateAccountRequest) {
const [res, cookie] = await internalApi.auth.login(
account.tenantId,
account.email,
account.password,
API_OPTS
)
// @ts-ignore
global.qa.authCookie = cookie
}
async function setup() {
console.log("\nGLOBAL SETUP STARTING")
const env = await internalApi.environment.getEnvironment(API_OPTS)
console.log(`Environment: ${JSON.stringify(env)}`)
if (env.multiTenancy) {
const account = await createAccount()
// @ts-ignore
global.qa.tenantId = account.tenantId
await loginAsAccount(account)
} else {
// @ts-ignore
global.qa.tenantId = DEFAULT_TENANT_ID
await loginAsAdmin()
}
// @ts-ignore
console.log(`Tenant: ${global.qa.tenantId}`)
console.log("GLOBAL SETUP COMPLETE")
}
export default setup

View File

@ -0,0 +1,9 @@
async function teardown() {
console.log("\nGLOBAL TEARDOWN STARTING")
// TODO: Delete account and apps after test run
console.log("GLOBAL TEARDOWN COMPLETE")
}
export default teardown

View File

@ -0,0 +1 @@
jest.setTimeout(60000)

View File

@ -0,0 +1,23 @@
import AppAPI from "./apis/AppAPI"
import UserAPI from "./apis/UserAPI"
import TableAPI from "./apis/TableAPI"
import RowAPI from "./apis/RowAPI"
import BudibasePublicAPIClient from "./BudibasePublicAPIClient"
import { State } from "../../types"
export default class BudibasePublicAPI {
client: BudibasePublicAPIClient
apps: AppAPI
users: UserAPI
tables: TableAPI
rows: RowAPI
constructor(state: State) {
this.client = new BudibasePublicAPIClient(state)
this.apps = new AppAPI(this.client)
this.users = new UserAPI(this.client)
this.tables = new TableAPI(this.client)
this.rows = new RowAPI(this.client, state)
}
}

View File

@ -0,0 +1,74 @@
import env from "../../environment"
import fetch, { HeadersInit } from "node-fetch"
import { State } from "../../types"
type APIMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"
interface ApiOptions {
method?: APIMethod
body?: object
headers?: HeadersInit | undefined
}
class BudibasePublicAPIClient {
state: State
host: string
constructor(state: State) {
if (!env.BUDIBASE_URL) {
throw new Error("Must set BUDIBASE_URL env var")
}
this.host = `${env.BUDIBASE_URL}/api/public/v1`
this.state = state
}
apiCall =
(method: APIMethod) =>
async (url = "", options: ApiOptions = {}) => {
const requestOptions = {
method,
body: JSON.stringify(options.body),
headers: {
"x-budibase-api-key": this.state.apiKey,
"x-budibase-app-id": this.state.appId,
"Content-Type": "application/json",
Accept: "application/json",
...options.headers,
redirect: "follow",
follow: 20,
},
}
// prettier-ignore
// @ts-ignore
const response = await fetch(`${this.host}${url}`, requestOptions)
let body: any
const contentType = response.headers.get("content-type")
if (contentType && contentType.includes("application/json")) {
body = await response.json()
} else {
body = await response.text()
}
const message = `${method} ${url} - ${response.status}
Response body: ${JSON.stringify(body)}
Request body: ${requestOptions.body}`
if (response.status > 499) {
console.error(message)
} else if (response.status >= 400) {
console.warn(message)
}
return [response, body]
}
post = this.apiCall("POST")
get = this.apiCall("GET")
patch = this.apiCall("PATCH")
del = this.apiCall("DELETE")
put = this.apiCall("PUT")
}
export default BudibasePublicAPIClient

View File

@ -0,0 +1,68 @@
import { Response } from "node-fetch"
import BudibasePublicAPIClient from "../BudibasePublicAPIClient"
import * as fixtures from "../../fixtures"
import {
Application,
SearchInputParams,
CreateApplicationParams,
} from "../../../types"
export default class AppAPI {
client: BudibasePublicAPIClient
constructor(client: BudibasePublicAPIClient) {
this.client = client
}
async seed(): Promise<[Response, Application]> {
return this.create(fixtures.apps.generateApp())
}
async create(
body: CreateApplicationParams
): Promise<[Response, Application]> {
const [response, json] = await this.client.post(`/applications`, { body })
return [response, json.data]
}
async read(id: string): Promise<[Response, Application]> {
const [response, json] = await this.client.get(`/applications/${id}`)
return [response, json.data]
}
async search(body: SearchInputParams): Promise<[Response, [Application]]> {
const [response, json] = await this.client.post(`/applications/search`, {
body,
})
return [response, json.data]
}
async update(
id: string,
body: Application
): Promise<[Response, Application]> {
const [response, json] = await this.client.put(`/applications/${id}`, {
body,
})
return [response, json.data]
}
async delete(id: string): Promise<[Response, Application]> {
const [response, json] = await this.client.del(`/applications/${id}`)
return [response, json.data]
}
async publish(id: string): Promise<[Response, any]> {
const [response, json] = await this.client.post(
`/applications/${id}/publish`
)
return [response, json.data]
}
async unpublish(id: string): Promise<[Response]> {
const [response, json] = await this.client.post(
`/applications/${id}/unpublish`
)
return [response]
}
}

View File

@ -0,0 +1,61 @@
import {
CreateRowParams,
Row,
SearchInputParams,
} from "@budibase/server/api/controllers/public/mapping/types"
import { HeadersInit, Response } from "node-fetch"
import BudibasePublicAPIClient from "../BudibasePublicAPIClient"
import * as fixtures from "../../fixtures"
import { State } from "../../../types"
export default class RowAPI {
client: BudibasePublicAPIClient
headers?: HeadersInit
state: State
constructor(client: BudibasePublicAPIClient, state: State) {
this.state = state
this.client = client
}
async seed(tableId: string) {
return this.create(fixtures.rows.generateRow({ tableId }))
}
async create(body: CreateRowParams): Promise<[Response, Row]> {
const [response, json] = await this.client.post(
`/tables/${this.state.tableId}/rows`,
{
body,
}
)
return [response, json.data]
}
async read(id: string): Promise<[Response, Row]> {
const [response, json] = await this.client.get(
`/tables/${this.state.tableId}/rows/${id}`
)
return [response, json.data]
}
async search(body: SearchInputParams): Promise<[Response, [Row]]> {
const [response, json] = await this.client.post(
`/tables/${this.state.tableId}/rows/search`,
{ body }
)
return [response, json.data]
}
async update(id: string, body: Row): Promise<[Response, Row]> {
const [response, json] = await this.client.put(
`/tables/${this.state.tableId}/rows/${id}`,
{
body,
}
)
return [response, json.data]
}
}

View File

@ -1,18 +1,19 @@
import PublicAPIClient from "./PublicAPIClient"
import {
Table,
SearchInputParams,
CreateTableParams,
} from "@budibase/server/api/controllers/public/mapping/types"
import { HeadersInit, Response } from "node-fetch"
import { generateTable } from "../fixtures/tables"
import { generateTable } from "../../fixtures/tables"
import BudibasePublicAPIClient from "../BudibasePublicAPIClient"
export default class TableApi {
api: PublicAPIClient
export default class TableAPI {
headers?: HeadersInit
constructor(apiClient: PublicAPIClient) {
this.api = apiClient
client: BudibasePublicAPIClient
constructor(client: BudibasePublicAPIClient) {
this.client = client
}
async seed() {
@ -20,28 +21,24 @@ export default class TableApi {
}
async create(body: CreateTableParams): Promise<[Response, Table]> {
const response = await this.api.post(`/tables`, {
const [response, json] = await this.client.post(`/tables`, {
body,
})
const json = await response.json()
return [response, json.data]
}
async read(id: string): Promise<[Response, Table]> {
const response = await this.api.get(`/tables/${id}`)
const json = await response.json()
const [response, json] = await this.client.get(`/tables/${id}`)
return [response, json.data]
}
async search(body: SearchInputParams): Promise<[Response, [Table]]> {
const response = await this.api.post(`/tables/search`, { body })
const json = await response.json()
const [response, json] = await this.client.post(`/tables/search`, { body })
return [response, json.data]
}
async update(id: string, body: Table): Promise<[Response, Table]> {
const response = await this.api.put(`/tables/${id}`, { body })
const json = await response.json()
const [response, json] = await this.client.put(`/tables/${id}`, { body })
return [response, json.data]
}
}

View File

@ -0,0 +1,40 @@
import {
CreateUserParams,
SearchInputParams,
User,
} from "@budibase/server/api/controllers/public/mapping/types"
import { Response } from "node-fetch"
import BudibasePublicAPIClient from "../BudibasePublicAPIClient"
import * as fixtures from "../../fixtures"
export default class UserAPI {
client: BudibasePublicAPIClient
constructor(client: BudibasePublicAPIClient) {
this.client = client
}
async seed() {
return this.create(fixtures.users.generateUser())
}
async create(body: CreateUserParams): Promise<[Response, User]> {
const [response, json] = await this.client.post(`/users`, { body })
return [response, json.data]
}
async read(id: string): Promise<[Response, User]> {
const [response, json] = await this.client.get(`/users/${id}`)
return [response, json.data]
}
async search(body: SearchInputParams): Promise<[Response, [User]]> {
const [response, json] = await this.client.post(`/users/search`, { body })
return [response, json.data]
}
async update(id: string, body: User): Promise<[Response, User]> {
const [response, json] = await this.client.put(`/users/${id}`, { body })
return [response, json.data]
}
}

View File

@ -0,0 +1 @@
export { default as BudibasePublicAPI } from "./BudibasePublicAPI"

View File

@ -0,0 +1,33 @@
import { BudibasePublicAPI } from "../api"
import { BudibaseTestConfiguration } from "../../shared"
export default class TestConfiguration<T> extends BudibaseTestConfiguration {
// apis
api: BudibasePublicAPI
context: T
constructor() {
super()
this.api = new BudibasePublicAPI(this.state)
this.context = <T>{}
}
// LIFECYCLE
async beforeAll() {
await super.beforeAll()
await this.setApiKey()
}
async afterAll() {
await super.afterAll()
}
// AUTH
async setApiKey() {
const apiKeyResponse = await this.internalApi.self.getApiKey()
this.state.apiKey = apiKeyResponse.apiKey
}
}

Some files were not shown because too many files have changed in this diff Show More