wip
This commit is contained in:
parent
e69bfd7eb5
commit
433c20d80c
|
@ -1,27 +1,44 @@
|
||||||
import * as setup from "../../tests/utilities"
|
import * as setup from "../../tests/utilities"
|
||||||
import { generateMakeRequest, MakeRequestResponse } from "./utils"
|
|
||||||
import { User } from "@budibase/types"
|
import { User } from "@budibase/types"
|
||||||
import { mocks } from "@budibase/backend-core/tests"
|
import { mocks } from "@budibase/backend-core/tests"
|
||||||
|
import nock from "nock"
|
||||||
|
import environment from "../../../../environment"
|
||||||
|
import TestConfiguration from "../../../../tests/utilities/TestConfiguration"
|
||||||
|
|
||||||
import * as workerRequests from "../../../../utilities/workerRequests"
|
const config = new TestConfiguration()
|
||||||
|
let globalUser: User
|
||||||
const mockedWorkerReq = jest.mocked(workerRequests)
|
|
||||||
|
|
||||||
let config = setup.getConfig()
|
|
||||||
let apiKey: string, globalUser: User, makeRequest: MakeRequestResponse
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await config.init()
|
await config.init()
|
||||||
globalUser = await config.globalUser()
|
|
||||||
apiKey = await config.generateApiKey(globalUser._id)
|
|
||||||
makeRequest = generateMakeRequest(apiKey)
|
|
||||||
mockedWorkerReq.readGlobalUser.mockImplementation(() =>
|
|
||||||
Promise.resolve(globalUser)
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(setup.afterAll)
|
afterAll(setup.afterAll)
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
globalUser = await config.globalUser()
|
||||||
|
|
||||||
|
nock.cleanAll()
|
||||||
|
nock(environment.WORKER_URL!)
|
||||||
|
.get(`/api/global/users/${globalUser._id}`)
|
||||||
|
.reply(200, (uri, body) => {
|
||||||
|
return globalUser
|
||||||
|
})
|
||||||
|
.persist()
|
||||||
|
|
||||||
|
nock(environment.WORKER_URL!)
|
||||||
|
.post(`/api/global/users`)
|
||||||
|
.reply(200, (uri, body) => {
|
||||||
|
const updatedUser = body as User
|
||||||
|
if (updatedUser._id === globalUser._id) {
|
||||||
|
globalUser = updatedUser
|
||||||
|
return globalUser
|
||||||
|
} else {
|
||||||
|
throw new Error("User not found")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.persist()
|
||||||
|
})
|
||||||
|
|
||||||
function base() {
|
function base() {
|
||||||
return {
|
return {
|
||||||
tenantId: config.getTenantId(),
|
tenantId: config.getTenantId(),
|
||||||
|
@ -30,37 +47,26 @@ function base() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateMock() {
|
describe.only("check user endpoints", () => {
|
||||||
mockedWorkerReq.readGlobalUser.mockImplementation(ctx => ctx.request.body)
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("check user endpoints", () => {
|
|
||||||
it("should not allow a user to update their own roles", async () => {
|
it("should not allow a user to update their own roles", async () => {
|
||||||
const res = await makeRequest("put", `/users/${globalUser._id}`, {
|
await config.withUser(globalUser, () =>
|
||||||
...globalUser,
|
config.api.public.user.update({
|
||||||
roles: {
|
...globalUser,
|
||||||
app_1: "ADMIN",
|
roles: { app_1: "ADMIN" },
|
||||||
},
|
})
|
||||||
})
|
)
|
||||||
expect(
|
const updatedUser = await config.api.user.find(globalUser._id!)
|
||||||
mockedWorkerReq.saveGlobalUser.mock.lastCall?.[0].body.data.roles["app_1"]
|
expect(updatedUser.roles?.app_1).toBeUndefined()
|
||||||
).toBeUndefined()
|
|
||||||
expect(res.status).toBe(200)
|
|
||||||
expect(res.body.data.roles["app_1"]).toBeUndefined()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should not allow a user to delete themselves", async () => {
|
it("should not allow a user to delete themselves", async () => {
|
||||||
const res = await makeRequest("delete", `/users/${globalUser._id}`)
|
await config.withUser(globalUser, () =>
|
||||||
expect(res.status).toBe(405)
|
config.api.public.user.destroy(globalUser._id!, { status: 405 })
|
||||||
expect(mockedWorkerReq.deleteGlobalUser.mock.lastCall).toBeUndefined()
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("no user role update in free", () => {
|
describe("no user role update in free", () => {
|
||||||
beforeAll(() => {
|
|
||||||
updateMock()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should not allow 'roles' to be updated", async () => {
|
it("should not allow 'roles' to be updated", async () => {
|
||||||
const res = await makeRequest("post", "/users", {
|
const res = await makeRequest("post", "/users", {
|
||||||
...base(),
|
...base(),
|
||||||
|
@ -94,7 +100,6 @@ describe("no user role update in free", () => {
|
||||||
|
|
||||||
describe("no user role update in business", () => {
|
describe("no user role update in business", () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
updateMock()
|
|
||||||
mocks.licenses.useExpandedPublicApi()
|
mocks.licenses.useExpandedPublicApi()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -3,44 +3,6 @@ import supertest from "supertest"
|
||||||
|
|
||||||
export * as structures from "../../../../tests/utilities/structures"
|
export * as structures from "../../../../tests/utilities/structures"
|
||||||
|
|
||||||
function user() {
|
|
||||||
return {
|
|
||||||
_id: "user",
|
|
||||||
_rev: "rev",
|
|
||||||
createdAt: Date.now(),
|
|
||||||
email: "test@example.com",
|
|
||||||
roles: {},
|
|
||||||
tenantId: "default",
|
|
||||||
status: "active",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jest.mock("../../../../utilities/workerRequests", () => ({
|
|
||||||
getGlobalUsers: jest.fn(() => {
|
|
||||||
return {
|
|
||||||
_id: "us_uuid1",
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
getGlobalSelf: jest.fn(() => {
|
|
||||||
return {
|
|
||||||
_id: "us_uuid1",
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
allGlobalUsers: jest.fn(() => {
|
|
||||||
return [user()]
|
|
||||||
}),
|
|
||||||
readGlobalUser: jest.fn(() => {
|
|
||||||
return user()
|
|
||||||
}),
|
|
||||||
saveGlobalUser: jest.fn(() => {
|
|
||||||
return { _id: "user", _rev: "rev" }
|
|
||||||
}),
|
|
||||||
deleteGlobalUser: jest.fn(() => {
|
|
||||||
return { message: "deleted user" }
|
|
||||||
}),
|
|
||||||
removeAppFromUserRoles: jest.fn(),
|
|
||||||
}))
|
|
||||||
|
|
||||||
export function delay(ms: number) {
|
export function delay(ms: number) {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms))
|
return new Promise(resolve => setTimeout(resolve, ms))
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@ import {
|
||||||
View,
|
View,
|
||||||
Webhook,
|
Webhook,
|
||||||
WithRequired,
|
WithRequired,
|
||||||
|
DevInfo,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
import API from "./api"
|
import API from "./api"
|
||||||
|
@ -248,7 +249,7 @@ export default class TestConfiguration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async withUser(user: User, f: () => Promise<void>) {
|
async withUser<T>(user: User, f: () => Promise<T>): Promise<T> {
|
||||||
const oldUser = this.user
|
const oldUser = this.user
|
||||||
this.user = user
|
this.user = user
|
||||||
try {
|
try {
|
||||||
|
@ -469,7 +470,10 @@ export default class TestConfiguration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultHeaders(extras = {}, prodApp = false) {
|
defaultHeaders(
|
||||||
|
extras: Record<string, string | string[]> = {},
|
||||||
|
prodApp = false
|
||||||
|
) {
|
||||||
const tenantId = this.getTenantId()
|
const tenantId = this.getTenantId()
|
||||||
const user = this.getUser()
|
const user = this.getUser()
|
||||||
const authObj: AuthToken = {
|
const authObj: AuthToken = {
|
||||||
|
@ -498,10 +502,13 @@ export default class TestConfiguration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
publicHeaders({ prodApp = true } = {}) {
|
publicHeaders({
|
||||||
|
prodApp = true,
|
||||||
|
extras = {},
|
||||||
|
}: { prodApp?: boolean; extras?: Record<string, string | string[]> } = {}) {
|
||||||
const appId = prodApp ? this.prodAppId : this.appId
|
const appId = prodApp ? this.prodAppId : this.appId
|
||||||
|
|
||||||
const headers: any = {
|
const headers: Record<string, string> = {
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
Cookie: "",
|
Cookie: "",
|
||||||
}
|
}
|
||||||
|
@ -514,6 +521,7 @@ export default class TestConfiguration {
|
||||||
return {
|
return {
|
||||||
...headers,
|
...headers,
|
||||||
...this.temporaryHeaders,
|
...this.temporaryHeaders,
|
||||||
|
...extras,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -577,17 +585,17 @@ export default class TestConfiguration {
|
||||||
}
|
}
|
||||||
const db = tenancy.getTenantDB(this.getTenantId())
|
const db = tenancy.getTenantDB(this.getTenantId())
|
||||||
const id = dbCore.generateDevInfoID(userId)
|
const id = dbCore.generateDevInfoID(userId)
|
||||||
let devInfo: any
|
const devInfo = await db.tryGet<DevInfo>(id)
|
||||||
try {
|
if (devInfo && devInfo.apiKey) {
|
||||||
devInfo = await db.get(id)
|
return devInfo.apiKey
|
||||||
} catch (err) {
|
|
||||||
devInfo = { _id: id, userId }
|
|
||||||
}
|
}
|
||||||
devInfo.apiKey = encryption.encrypt(
|
|
||||||
|
const apiKey = encryption.encrypt(
|
||||||
`${this.getTenantId()}${dbCore.SEPARATOR}${newid()}`
|
`${this.getTenantId()}${dbCore.SEPARATOR}${newid()}`
|
||||||
)
|
)
|
||||||
await db.put(devInfo)
|
const newDevInfo: DevInfo = { _id: id, userId, apiKey }
|
||||||
return devInfo.apiKey
|
await db.put(newDevInfo)
|
||||||
|
return apiKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// APP
|
// APP
|
||||||
|
|
|
@ -46,6 +46,7 @@ export interface RequestOpts {
|
||||||
export abstract class TestAPI {
|
export abstract class TestAPI {
|
||||||
config: TestConfiguration
|
config: TestConfiguration
|
||||||
request: SuperTest<Test>
|
request: SuperTest<Test>
|
||||||
|
prefix = ""
|
||||||
|
|
||||||
constructor(config: TestConfiguration) {
|
constructor(config: TestConfiguration) {
|
||||||
this.config = config
|
this.config = config
|
||||||
|
@ -53,26 +54,26 @@ export abstract class TestAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _get = async <T>(url: string, opts?: RequestOpts): Promise<T> => {
|
protected _get = async <T>(url: string, opts?: RequestOpts): Promise<T> => {
|
||||||
return await this._request<T>("get", url, opts)
|
return await this._request<T>("get", `${this.prefix}${url}`, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _post = async <T>(url: string, opts?: RequestOpts): Promise<T> => {
|
protected _post = async <T>(url: string, opts?: RequestOpts): Promise<T> => {
|
||||||
return await this._request<T>("post", url, opts)
|
return await this._request<T>("post", `${this.prefix}${url}`, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _put = async <T>(url: string, opts?: RequestOpts): Promise<T> => {
|
protected _put = async <T>(url: string, opts?: RequestOpts): Promise<T> => {
|
||||||
return await this._request<T>("put", url, opts)
|
return await this._request<T>("put", `${this.prefix}${url}`, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _patch = async <T>(url: string, opts?: RequestOpts): Promise<T> => {
|
protected _patch = async <T>(url: string, opts?: RequestOpts): Promise<T> => {
|
||||||
return await this._request<T>("patch", url, opts)
|
return await this._request<T>("patch", `${this.prefix}${url}`, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _delete = async <T>(
|
protected _delete = async <T>(
|
||||||
url: string,
|
url: string,
|
||||||
opts?: RequestOpts
|
opts?: RequestOpts
|
||||||
): Promise<T> => {
|
): Promise<T> => {
|
||||||
return await this._request<T>("delete", url, opts)
|
return await this._request<T>("delete", `${this.prefix}${url}`, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _requestRaw = async (
|
protected _requestRaw = async (
|
||||||
|
@ -88,7 +89,6 @@ export abstract class TestAPI {
|
||||||
fields = {},
|
fields = {},
|
||||||
files = {},
|
files = {},
|
||||||
expectations,
|
expectations,
|
||||||
publicUser = false,
|
|
||||||
} = opts || {}
|
} = opts || {}
|
||||||
const { status = 200 } = expectations || {}
|
const { status = 200 } = expectations || {}
|
||||||
const expectHeaders = expectations?.headers || {}
|
const expectHeaders = expectations?.headers || {}
|
||||||
|
@ -97,7 +97,7 @@ export abstract class TestAPI {
|
||||||
expectHeaders["Content-Type"] = /^application\/json/
|
expectHeaders["Content-Type"] = /^application\/json/
|
||||||
}
|
}
|
||||||
|
|
||||||
let queryParams = []
|
let queryParams: string[] = []
|
||||||
for (const [key, value] of Object.entries(query)) {
|
for (const [key, value] of Object.entries(query)) {
|
||||||
if (value) {
|
if (value) {
|
||||||
queryParams.push(`${key}=${value}`)
|
queryParams.push(`${key}=${value}`)
|
||||||
|
@ -107,18 +107,10 @@ export abstract class TestAPI {
|
||||||
url += `?${queryParams.join("&")}`
|
url += `?${queryParams.join("&")}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const headersFn = publicUser
|
|
||||||
? (_extras = {}) =>
|
|
||||||
this.config.publicHeaders.bind(this.config)({
|
|
||||||
prodApp: opts?.useProdApp,
|
|
||||||
})
|
|
||||||
: (extras = {}) =>
|
|
||||||
this.config.defaultHeaders.bind(this.config)(extras, opts?.useProdApp)
|
|
||||||
|
|
||||||
const app = getServer()
|
const app = getServer()
|
||||||
let req = request(app)[method](url)
|
let req = request(app)[method](url)
|
||||||
req = req.set(
|
req = req.set(
|
||||||
headersFn({
|
await this.getHeaders(opts, {
|
||||||
"x-budibase-include-stacktrace": "true",
|
"x-budibase-include-stacktrace": "true",
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -167,6 +159,17 @@ export abstract class TestAPI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async getHeaders(
|
||||||
|
opts?: RequestOpts,
|
||||||
|
extras?: Record<string, string | string[]>
|
||||||
|
): Promise<Record<string, string | string[]>> {
|
||||||
|
if (opts?.publicUser) {
|
||||||
|
return this.config.publicHeaders({ prodApp: opts?.useProdApp, extras })
|
||||||
|
} else {
|
||||||
|
return this.config.defaultHeaders(extras, opts?.useProdApp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected _checkResponse = (
|
protected _checkResponse = (
|
||||||
response: Response,
|
response: Response,
|
||||||
expectations?: Expectations
|
expectations?: Expectations
|
||||||
|
@ -236,3 +239,24 @@ export abstract class TestAPI {
|
||||||
).body
|
).body
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export abstract class PublicAPI extends TestAPI {
|
||||||
|
prefix = "/api/public/v1"
|
||||||
|
|
||||||
|
protected async getHeaders(
|
||||||
|
opts?: RequestOpts,
|
||||||
|
extras?: Record<string, string | string[]>
|
||||||
|
): Promise<Record<string, string | string[]>> {
|
||||||
|
const apiKey = await this.config.generateApiKey()
|
||||||
|
|
||||||
|
const headers: Record<string, string | string[]> = {
|
||||||
|
Accept: "application/json",
|
||||||
|
Host: this.config.tenantHost(),
|
||||||
|
"x-budibase-api-key": apiKey,
|
||||||
|
"x-budibase-app-id": this.config.getAppId(),
|
||||||
|
...extras,
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { RowActionAPI } from "./rowAction"
|
||||||
import { AutomationAPI } from "./automation"
|
import { AutomationAPI } from "./automation"
|
||||||
import { PluginAPI } from "./plugin"
|
import { PluginAPI } from "./plugin"
|
||||||
import { WebhookAPI } from "./webhook"
|
import { WebhookAPI } from "./webhook"
|
||||||
|
import { UserPublicAPI } from "./public/user"
|
||||||
|
|
||||||
export default class API {
|
export default class API {
|
||||||
application: ApplicationAPI
|
application: ApplicationAPI
|
||||||
|
@ -38,6 +39,10 @@ export default class API {
|
||||||
viewV2: ViewV2API
|
viewV2: ViewV2API
|
||||||
webhook: WebhookAPI
|
webhook: WebhookAPI
|
||||||
|
|
||||||
|
public: {
|
||||||
|
user: UserPublicAPI
|
||||||
|
}
|
||||||
|
|
||||||
constructor(config: TestConfiguration) {
|
constructor(config: TestConfiguration) {
|
||||||
this.application = new ApplicationAPI(config)
|
this.application = new ApplicationAPI(config)
|
||||||
this.attachment = new AttachmentAPI(config)
|
this.attachment = new AttachmentAPI(config)
|
||||||
|
@ -57,5 +62,8 @@ export default class API {
|
||||||
this.user = new UserAPI(config)
|
this.user = new UserAPI(config)
|
||||||
this.viewV2 = new ViewV2API(config)
|
this.viewV2 = new ViewV2API(config)
|
||||||
this.webhook = new WebhookAPI(config)
|
this.webhook = new WebhookAPI(config)
|
||||||
|
this.public = {
|
||||||
|
user: new UserPublicAPI(config),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { User } from "@budibase/types"
|
||||||
|
import { Expectations, PublicAPI } from "../base"
|
||||||
|
|
||||||
|
export class UserPublicAPI extends PublicAPI {
|
||||||
|
find = async (id: string, expectations?: Expectations): Promise<User> => {
|
||||||
|
return await this._get<User>(`/users/${id}`, { expectations })
|
||||||
|
}
|
||||||
|
|
||||||
|
update = async (user: User, expectations?: Expectations): Promise<User> => {
|
||||||
|
return await this._put<User>(`/users/${user._id}`, {
|
||||||
|
body: user,
|
||||||
|
expectations,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy = async (id: string, expectations?: Expectations): Promise<void> => {
|
||||||
|
return await this._delete(`/users/${id}`, { expectations })
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue