Fix compare.spec.ts.

This commit is contained in:
Sam Rose 2025-02-27 17:11:50 +00:00
parent d7f3ad8a84
commit 55783e17f1
No known key found for this signature in database
6 changed files with 362 additions and 269 deletions

View File

@ -0,0 +1,21 @@
import { object } from "./utils"
import Resource from "./utils/Resource"
const errorSchema = object({
status: {
type: "number",
description: "The HTTP status code of the error.",
},
message: {
type: "string",
description: "A descriptive message about the error.",
},
})
export default new Resource()
.setExamples({
error: {},
})
.setSchemas({
error: errorSchema,
})

View File

@ -2,184 +2,174 @@ import jestOpenAPI from "jest-openapi"
import { run as generateSchema } from "../../../../../specs/generate" import { run as generateSchema } from "../../../../../specs/generate"
import * as setup from "../../tests/utilities" import * as setup from "../../tests/utilities"
import { generateMakeRequest } from "./utils" import { generateMakeRequest } from "./utils"
import { Table, App, Row, User } from "@budibase/types" import { Table, App, Row } from "@budibase/types"
import nock from "nock"
import environment from "../../../../environment"
const yamlPath = generateSchema() const yamlPath = generateSchema()
jestOpenAPI(yamlPath!) jestOpenAPI(yamlPath!)
let config = setup.getConfig() describe("compare", () => {
let apiKey: string, table: Table, app: App, makeRequest: any let config = setup.getConfig()
let apiKey: string, table: Table, app: App, makeRequest: any
beforeAll(async () => { beforeAll(async () => {
app = await config.init() app = await config.init()
table = await config.upsertTable()
apiKey = await config.generateApiKey()
makeRequest = generateMakeRequest(apiKey)
})
afterAll(setup.afterAll)
describe("check the applications endpoints", () => {
it("should allow retrieving applications through search", async () => {
const res = await makeRequest("post", "/applications/search")
expect(res).toSatisfyApiSpec()
})
it("should allow creating an application", async () => {
const res = await makeRequest(
"post",
"/applications",
{
name: "new App",
},
null
)
expect(res).toSatisfyApiSpec()
})
it("should allow updating an application", async () => {
const app = config.getApp()
const appId = config.getAppId()
const res = await makeRequest(
"put",
`/applications/${appId}`,
{
...app,
name: "updated app name",
},
appId
)
expect(res).toSatisfyApiSpec()
})
it("should allow retrieving an application", async () => {
const res = await makeRequest("get", `/applications/${config.getAppId()}`)
expect(res).toSatisfyApiSpec()
})
it("should allow deleting an application", async () => {
const res = await makeRequest(
"delete",
`/applications/${config.getAppId()}`
)
expect(res).toSatisfyApiSpec()
})
})
describe("check the tables endpoints", () => {
it("should allow retrieving tables through search", async () => {
await config.createApp("new app 1")
table = await config.upsertTable() table = await config.upsertTable()
const res = await makeRequest("post", "/tables/search") apiKey = await config.generateApiKey()
expect(res).toSatisfyApiSpec() makeRequest = generateMakeRequest(apiKey)
}) })
it("should allow creating a table", async () => { afterAll(setup.afterAll)
const res = await makeRequest("post", "/tables", {
name: "table name", beforeEach(() => {
primaryDisplay: "column1", nock.cleanAll()
schema: { })
column1: {
type: "string", describe("check the applications endpoints", () => {
constraints: {}, it("should allow retrieving applications through search", async () => {
const res = await makeRequest("post", "/applications/search")
expect(res).toSatisfyApiSpec()
})
it("should allow creating an application", async () => {
const res = await makeRequest(
"post",
"/applications",
{
name: "new App",
}, },
}, null
)
expect(res).toSatisfyApiSpec()
}) })
expect(res).toSatisfyApiSpec()
})
it("should allow updating a table", async () => { it("should allow updating an application", async () => {
const updated = { ...table, _rev: undefined, name: "new name" } const app = config.getApp()
const res = await makeRequest("put", `/tables/${table._id}`, updated) const appId = config.getAppId()
expect(res).toSatisfyApiSpec() const res = await makeRequest(
}) "put",
`/applications/${appId}`,
it("should allow retrieving a table", async () => { {
const res = await makeRequest("get", `/tables/${table._id}`) ...app,
expect(res).toSatisfyApiSpec() name: "updated app name",
}) },
appId
it("should allow deleting a table", async () => { )
const res = await makeRequest("delete", `/tables/${table._id}`) expect(res).toSatisfyApiSpec()
expect(res).toSatisfyApiSpec()
})
})
describe("check the rows endpoints", () => {
let row: Row
it("should allow retrieving rows through search", async () => {
table = await config.upsertTable()
const res = await makeRequest("post", `/tables/${table._id}/rows/search`, {
query: {},
}) })
expect(res).toSatisfyApiSpec()
})
it("should allow creating a row", async () => { it("should allow retrieving an application", async () => {
const res = await makeRequest("post", `/tables/${table._id}/rows`, { const res = await makeRequest("get", `/applications/${config.getAppId()}`)
name: "test row", expect(res).toSatisfyApiSpec()
})
it("should allow deleting an application", async () => {
nock(environment.WORKER_URL!)
.delete(`/api/global/roles/${config.getProdAppId()}`)
.reply(200, {})
const res = await makeRequest(
"delete",
`/applications/${config.getAppId()}`
)
expect(res).toSatisfyApiSpec()
}) })
expect(res).toSatisfyApiSpec()
row = res.body.data
}) })
it("should allow updating a row", async () => { describe("check the tables endpoints", () => {
const res = await makeRequest( it("should allow retrieving tables through search", async () => {
"put", await config.createApp("new app 1")
`/tables/${table._id}/rows/${row._id}`, table = await config.upsertTable()
{ const res = await makeRequest("post", "/tables/search")
name: "test row updated", expect(res).toSatisfyApiSpec()
} })
)
expect(res).toSatisfyApiSpec() it("should allow creating a table", async () => {
const res = await makeRequest("post", "/tables", {
name: "table name",
primaryDisplay: "column1",
schema: {
column1: {
type: "string",
constraints: {},
},
},
})
expect(res).toSatisfyApiSpec()
})
it("should allow updating a table", async () => {
const updated = { ...table, _rev: undefined, name: "new name" }
const res = await makeRequest("put", `/tables/${table._id}`, updated)
expect(res).toSatisfyApiSpec()
})
it("should allow retrieving a table", async () => {
const res = await makeRequest("get", `/tables/${table._id}`)
expect(res).toSatisfyApiSpec()
})
it("should allow deleting a table", async () => {
const res = await makeRequest("delete", `/tables/${table._id}`)
expect(res).toSatisfyApiSpec()
})
}) })
it("should allow retrieving a row", async () => { describe("check the rows endpoints", () => {
const res = await makeRequest("get", `/tables/${table._id}/rows/${row._id}`) let row: Row
expect(res).toSatisfyApiSpec() it("should allow retrieving rows through search", async () => {
table = await config.upsertTable()
const res = await makeRequest(
"post",
`/tables/${table._id}/rows/search`,
{
query: {},
}
)
expect(res).toSatisfyApiSpec()
})
it("should allow creating a row", async () => {
const res = await makeRequest("post", `/tables/${table._id}/rows`, {
name: "test row",
})
expect(res).toSatisfyApiSpec()
row = res.body.data
})
it("should allow updating a row", async () => {
const res = await makeRequest(
"put",
`/tables/${table._id}/rows/${row._id}`,
{
name: "test row updated",
}
)
expect(res).toSatisfyApiSpec()
})
it("should allow retrieving a row", async () => {
const res = await makeRequest(
"get",
`/tables/${table._id}/rows/${row._id}`
)
expect(res).toSatisfyApiSpec()
})
it("should allow deleting a row", async () => {
const res = await makeRequest(
"delete",
`/tables/${table._id}/rows/${row._id}`
)
expect(res).toSatisfyApiSpec()
})
}) })
it("should allow deleting a row", async () => { describe("check the queries endpoints", () => {
const res = await makeRequest( it("should allow retrieving queries through search", async () => {
"delete", const res = await makeRequest("post", "/queries/search")
`/tables/${table._id}/rows/${row._id}` expect(res).toSatisfyApiSpec()
) })
expect(res).toSatisfyApiSpec()
})
})
describe("check the users endpoints", () => {
let user: User
it("should allow retrieving users through search", async () => {
user = await config.createUser()
const res = await makeRequest("post", "/users/search")
expect(res).toSatisfyApiSpec()
})
it("should allow creating a user", async () => {
const res = await makeRequest("post", "/users")
expect(res).toSatisfyApiSpec()
})
it("should allow updating a user", async () => {
const res = await makeRequest("put", `/users/${user._id}`)
expect(res).toSatisfyApiSpec()
})
it("should allow retrieving a user", async () => {
const res = await makeRequest("get", `/users/${user._id}`)
expect(res).toSatisfyApiSpec()
})
it("should allow deleting a user", async () => {
const res = await makeRequest("delete", `/users/${user._id}`)
expect(res).toSatisfyApiSpec()
})
})
describe("check the queries endpoints", () => {
it("should allow retrieving queries through search", async () => {
const res = await makeRequest("post", "/queries/search")
expect(res).toSatisfyApiSpec()
}) })
}) })

View File

@ -2,120 +2,142 @@ import * as setup from "../../tests/utilities"
import { User } from "@budibase/types" import { User } from "@budibase/types"
import { generator, mocks } from "@budibase/backend-core/tests" import { generator, mocks } from "@budibase/backend-core/tests"
import nock from "nock" import nock from "nock"
import environment from "../../../../environment"
import TestConfiguration from "../../../../tests/utilities/TestConfiguration" import TestConfiguration from "../../../../tests/utilities/TestConfiguration"
import { mockWorkerUserAPI } from "./utils"
const config = new TestConfiguration() describe("public users API", () => {
let globalUser: User const config = new TestConfiguration()
let users: Record<string, User> = {} let globalUser: User
beforeAll(async () => { beforeAll(async () => {
await config.init() await config.init()
}) })
afterAll(setup.afterAll) afterAll(setup.afterAll)
beforeEach(async () => { beforeEach(async () => {
globalUser = await config.globalUser() globalUser = await config.globalUser()
users[globalUser._id!] = globalUser
nock.cleanAll() nock.cleanAll()
nock(environment.WORKER_URL!) mockWorkerUserAPI(globalUser)
.get(new RegExp(`/api/global/users/.*`)) })
.reply(200, (uri, body) => {
const id = uri.split("/").pop() describe("read", () => {
return users[id!] it("should allow a user to read themselves", async () => {
const user = await config.api.user.find(globalUser._id!)
expect(user._id).toBe(globalUser._id)
}) })
.persist()
nock(environment.WORKER_URL!) it("should allow a user to read another user", async () => {
.post(`/api/global/users`) const otherUser = await config.api.public.user.create({
.reply(200, (uri, body) => { email: generator.email({ domain: "example.com" }),
const newUser = body as User roles: {},
if (!newUser._id) {
newUser._id = `us_${generator.guid()}`
}
users[newUser._id!] = newUser
return newUser
})
.persist()
})
describe("check user endpoints", () => {
it("should not allow a user to update their own roles", async () => {
await config.withUser(globalUser, () =>
config.api.public.user.update({
...globalUser,
roles: { app_1: "ADMIN" },
}) })
) const user = await config.withUser(globalUser, () =>
const updatedUser = await config.api.user.find(globalUser._id!) config.api.public.user.find(otherUser._id!)
expect(updatedUser.roles?.app_1).toBeUndefined() )
expect(user._id).toBe(otherUser._id)
})
}) })
it("should not allow a user to delete themselves", async () => { describe("create", () => {
await config.withUser(globalUser, () => it("can successfully create a new user", async () => {
config.api.public.user.destroy(globalUser._id!, { status: 405 }) const email = generator.email({ domain: "example.com" })
) const newUser = await config.api.public.user.create({
}) email,
}) roles: {},
})
describe("role updating on free tier", () => { expect(newUser.email).toBe(email)
it("should not allow 'roles' to be updated", async () => { expect(newUser._id).toBeDefined()
const newUser = await config.api.public.user.create({ })
email: generator.email({ domain: "example.com" }),
roles: { app_a: "BASIC" }, describe("role creation on free tier", () => {
}) it("should not allow 'roles' to be updated", async () => {
expect(newUser.roles["app_a"]).toBeUndefined() const newUser = await config.api.public.user.create({
}) email: generator.email({ domain: "example.com" }),
roles: { app_a: "BASIC" },
it("should not allow 'admin' to be updated", async () => { })
const newUser = await config.api.public.user.create({ expect(newUser.roles["app_a"]).toBeUndefined()
email: generator.email({ domain: "example.com" }), })
roles: {},
admin: { global: true }, it("should not allow 'admin' to be updated", async () => {
}) const newUser = await config.api.public.user.create({
expect(newUser.admin).toBeUndefined() email: generator.email({ domain: "example.com" }),
}) roles: {},
admin: { global: true },
it("should not allow 'builder' to be updated", async () => { })
const newUser = await config.api.public.user.create({ expect(newUser.admin).toBeUndefined()
email: generator.email({ domain: "example.com" }), })
roles: {},
builder: { global: true }, it("should not allow 'builder' to be updated", async () => {
}) const newUser = await config.api.public.user.create({
expect(newUser.builder).toBeUndefined() email: generator.email({ domain: "example.com" }),
}) roles: {},
}) builder: { global: true },
})
describe("role updating on business tier", () => { expect(newUser.builder).toBeUndefined()
beforeAll(() => { })
mocks.licenses.useExpandedPublicApi() })
})
describe("role creation on business tier", () => {
it("should allow 'roles' to be updated", async () => { beforeAll(() => {
const newUser = await config.api.public.user.create({ mocks.licenses.useExpandedPublicApi()
email: generator.email({ domain: "example.com" }), })
roles: { app_a: "BASIC" },
}) it("should allow 'roles' to be updated", async () => {
expect(newUser.roles["app_a"]).toBe("BASIC") const newUser = await config.api.public.user.create({
}) email: generator.email({ domain: "example.com" }),
roles: { app_a: "BASIC" },
it("should allow 'admin' to be updated", async () => { })
const newUser = await config.api.public.user.create({ expect(newUser.roles["app_a"]).toBe("BASIC")
email: generator.email({ domain: "example.com" }), })
roles: {},
admin: { global: true }, it("should allow 'admin' to be updated", async () => {
}) const newUser = await config.api.public.user.create({
expect(newUser.admin?.global).toBe(true) email: generator.email({ domain: "example.com" }),
}) roles: {},
admin: { global: true },
it("should allow 'builder' to be updated", async () => { })
const newUser = await config.api.public.user.create({ expect(newUser.admin?.global).toBe(true)
email: generator.email({ domain: "example.com" }), })
roles: {},
builder: { global: true }, it("should allow 'builder' to be updated", async () => {
}) const newUser = await config.api.public.user.create({
expect(newUser.builder?.global).toBe(true) email: generator.email({ domain: "example.com" }),
roles: {},
builder: { global: true },
})
expect(newUser.builder?.global).toBe(true)
})
})
})
describe("update", () => {
it("can update a user", async () => {
const updatedUser = await config.api.public.user.update({
...globalUser,
email: `updated-${globalUser.email}`,
})
expect(updatedUser.email).toBe(`updated-${globalUser.email}`)
})
it("should not allow a user to update their own roles", async () => {
await config.withUser(globalUser, () =>
config.api.public.user.update({
...globalUser,
roles: { app_1: "ADMIN" },
})
)
const updatedUser = await config.api.user.find(globalUser._id!)
expect(updatedUser.roles?.app_1).toBeUndefined()
})
})
describe("delete", () => {
it("should not allow a user to delete themselves", async () => {
await config.withUser(globalUser, () =>
config.api.public.user.destroy(globalUser._id!, { status: 405 })
)
})
}) })
}) })

View File

@ -1,6 +1,10 @@
import * as setup from "../../tests/utilities" import * as setup from "../../tests/utilities"
import { checkSlashesInUrl } from "../../../../utilities" import { checkSlashesInUrl } from "../../../../utilities"
import supertest from "supertest" import supertest from "supertest"
import { User } from "@budibase/types"
import environment from "../../../../environment"
import nock from "nock"
import { generator } from "@budibase/backend-core/tests"
export type HttpMethod = "post" | "get" | "put" | "delete" | "patch" export type HttpMethod = "post" | "get" | "put" | "delete" | "patch"
@ -91,3 +95,43 @@ export function generateMakeRequestWithFormData(
return res return res
} }
} }
export function mockWorkerUserAPI(...seedUsers: User[]) {
const users: Record<string, User> = {
...seedUsers.reduce((acc, user) => {
acc[user._id!] = user
return acc
}, {} as Record<string, User>),
}
nock(environment.WORKER_URL!)
.get(new RegExp(`/api/global/users/.*`))
.reply(200, (uri, body) => {
const id = uri.split("/").pop()
return users[id!]
})
.persist()
nock(environment.WORKER_URL!)
.post(`/api/global/users`)
.reply(200, (uri, body) => {
const newUser = body as User
if (!newUser._id) {
newUser._id = `us_${generator.guid()}`
}
users[newUser._id!] = newUser
return newUser
})
.persist()
nock(environment.WORKER_URL!)
.put(new RegExp(`/api/global/users/.*`))
.reply(200, (uri, body) => {
const id = uri.split("/").pop()!
const updatedUser = body as User
const existingUser = users[id] || {}
users[id] = { ...existingUser, ...updatedUser }
return users[id]
})
.persist()
}

View File

@ -1,8 +1,13 @@
import jestOpenAPI from "jest-openapi"
import { run as generateSchema } from "../../../../specs/generate"
import TestConfiguration from "../TestConfiguration" import TestConfiguration from "../TestConfiguration"
import request, { SuperTest, Test, Response } from "supertest" import request, { SuperTest, Test, Response } from "supertest"
import { ReadStream } from "fs" import { ReadStream } from "fs"
import { getServer } from "../../../app" import { getServer } from "../../../app"
const yamlPath = generateSchema()
jestOpenAPI(yamlPath!)
type Headers = Record<string, string | string[] | undefined> type Headers = Record<string, string | string[] | undefined>
type Method = "get" | "post" | "put" | "patch" | "delete" type Method = "get" | "post" | "put" | "patch" | "delete"
@ -170,10 +175,7 @@ export abstract class TestAPI {
} }
} }
protected _checkResponse = ( protected _checkResponse(response: Response, expectations?: Expectations) {
response: Response,
expectations?: Expectations
) => {
const { status = 200 } = expectations || {} const { status = 200 } = expectations || {}
if (response.status !== status) { if (response.status !== status) {
@ -259,4 +261,14 @@ export abstract class PublicAPI extends TestAPI {
return headers return headers
} }
protected _checkResponse(response: Response, expectations?: Expectations) {
const checked = super._checkResponse(response, expectations)
if (checked.status >= 200 && checked.status < 300) {
// We don't seem to have documented our errors yet, so for the time being
// we'll only do the schema check for successful responses.
expect(checked).toSatisfyApiSpec()
}
return checked
}
} }

View File

@ -3,14 +3,18 @@ import { Expectations, PublicAPI } from "../base"
export class UserPublicAPI extends PublicAPI { export class UserPublicAPI extends PublicAPI {
find = async (id: string, expectations?: Expectations): Promise<User> => { find = async (id: string, expectations?: Expectations): Promise<User> => {
return await this._get<User>(`/users/${id}`, { expectations }) const response = await this._get<{ data: User }>(`/users/${id}`, {
expectations,
})
return response.data
} }
update = async (user: User, expectations?: Expectations): Promise<User> => { update = async (user: User, expectations?: Expectations): Promise<User> => {
return await this._put<User>(`/users/${user._id}`, { const response = await this._put<{ data: User }>(`/users/${user._id}`, {
body: user, body: user,
expectations, expectations,
}) })
return response.data
} }
destroy = async (id: string, expectations?: Expectations): Promise<void> => { destroy = async (id: string, expectations?: Expectations): Promise<void> => {