forked from tsai/budibase
committed by
GitHub
10 changed files with 669 additions and 4 deletions
@ -0,0 +1,28 @@ |
|||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP |
||||
|
|
||||
|
exports[`Authenticated middleware sets the correct APP auth type information when the user is not in the builder 1`] = ` |
||||
|
Object { |
||||
|
"apiKey": "1234", |
||||
|
"appId": "budibase:app:local", |
||||
|
"role": Role { |
||||
|
"_id": "ADMIN", |
||||
|
"inherits": "POWER", |
||||
|
"name": "Admin", |
||||
|
"permissionId": "admin", |
||||
|
}, |
||||
|
"roleId": "ADMIN", |
||||
|
} |
||||
|
`; |
||||
|
|
||||
|
exports[`Authenticated middleware sets the correct BUILDER auth type information when the x-budibase-type header is not 'client' 1`] = ` |
||||
|
Object { |
||||
|
"apiKey": "1234", |
||||
|
"appId": "budibase:builder:local", |
||||
|
"role": Role { |
||||
|
"_id": "BUILDER", |
||||
|
"name": "Builder", |
||||
|
"permissionId": "admin", |
||||
|
}, |
||||
|
"roleId": "BUILDER", |
||||
|
} |
||||
|
`; |
||||
@ -0,0 +1,126 @@ |
|||||
|
const { AuthTypes } = require("../../constants") |
||||
|
const authenticatedMiddleware = require("../authenticated") |
||||
|
const jwt = require("jsonwebtoken") |
||||
|
jest.mock("jsonwebtoken") |
||||
|
|
||||
|
class TestConfiguration { |
||||
|
constructor(middleware) { |
||||
|
this.middleware = authenticatedMiddleware |
||||
|
this.ctx = { |
||||
|
config: {}, |
||||
|
auth: {}, |
||||
|
request: {}, |
||||
|
cookies: { |
||||
|
set: jest.fn(), |
||||
|
get: jest.fn() |
||||
|
}, |
||||
|
headers: {}, |
||||
|
params: {}, |
||||
|
path: "", |
||||
|
request: { |
||||
|
headers: {} |
||||
|
}, |
||||
|
throw: jest.fn() |
||||
|
} |
||||
|
this.next = jest.fn() |
||||
|
} |
||||
|
|
||||
|
setHeaders(headers) { |
||||
|
this.ctx.headers = headers |
||||
|
} |
||||
|
|
||||
|
executeMiddleware() { |
||||
|
return this.middleware(this.ctx, this.next) |
||||
|
} |
||||
|
|
||||
|
afterEach() { |
||||
|
jest.resetAllMocks() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
describe("Authenticated middleware", () => { |
||||
|
let config |
||||
|
|
||||
|
beforeEach(() => { |
||||
|
config = new TestConfiguration() |
||||
|
}) |
||||
|
|
||||
|
afterEach(() => { |
||||
|
config.afterEach() |
||||
|
}) |
||||
|
|
||||
|
it("calls next() when on the builder path", async () => { |
||||
|
config.ctx.path = "/_builder" |
||||
|
|
||||
|
await config.executeMiddleware() |
||||
|
|
||||
|
expect(config.next).toHaveBeenCalled() |
||||
|
}) |
||||
|
|
||||
|
it("sets a new cookie when the current cookie does not match the app id from context", async () => { |
||||
|
const appId = "app_123" |
||||
|
config.setHeaders({ |
||||
|
"x-budibase-app-id": appId |
||||
|
}) |
||||
|
config.ctx.cookies.get.mockImplementation(() => "cookieAppId") |
||||
|
|
||||
|
await config.executeMiddleware() |
||||
|
|
||||
|
expect(config.ctx.cookies.set).toHaveBeenCalledWith( |
||||
|
"budibase:currentapp:local", |
||||
|
appId, |
||||
|
expect.any(Object) |
||||
|
) |
||||
|
|
||||
|
}) |
||||
|
|
||||
|
it("sets the correct BUILDER auth type information when the x-budibase-type header is not 'client'", async () => { |
||||
|
config.ctx.cookies.get.mockImplementation(() => "budibase:builder:local") |
||||
|
jwt.verify.mockImplementationOnce(() => ({ |
||||
|
apiKey: "1234", |
||||
|
roleId: "BUILDER" |
||||
|
})) |
||||
|
|
||||
|
await config.executeMiddleware() |
||||
|
|
||||
|
expect(config.ctx.auth.authenticated).toEqual(AuthTypes.BUILDER) |
||||
|
expect(config.ctx.user).toMatchSnapshot() |
||||
|
}) |
||||
|
|
||||
|
it("sets the correct APP auth type information when the user is not in the builder", async () => { |
||||
|
config.setHeaders({ |
||||
|
"x-budibase-type": "client" |
||||
|
}) |
||||
|
config.ctx.cookies.get.mockImplementation(() => `budibase:app:local`) |
||||
|
jwt.verify.mockImplementationOnce(() => ({ |
||||
|
apiKey: "1234", |
||||
|
roleId: "ADMIN" |
||||
|
})) |
||||
|
|
||||
|
await config.executeMiddleware() |
||||
|
|
||||
|
expect(config.ctx.auth.authenticated).toEqual(AuthTypes.APP) |
||||
|
expect(config.ctx.user).toMatchSnapshot() |
||||
|
}) |
||||
|
|
||||
|
it("marks the user as unauthenticated when a token cannot be determined from the users cookie", async () => { |
||||
|
config.executeMiddleware() |
||||
|
expect(config.ctx.auth.authenticated).toBe(false) |
||||
|
expect(config.ctx.user.role).toEqual({ |
||||
|
_id: "PUBLIC", |
||||
|
name: "Public", |
||||
|
permissionId: "public" |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
it("clears the cookie when there is an error authenticating in the builder", async () => { |
||||
|
config.ctx.cookies.get.mockImplementation(() => "budibase:builder:local") |
||||
|
jwt.verify.mockImplementationOnce(() => { |
||||
|
throw new Error() |
||||
|
}) |
||||
|
|
||||
|
await config.executeMiddleware() |
||||
|
|
||||
|
expect(config.ctx.cookies.set).toBeCalledWith("budibase:builder:local") |
||||
|
}) |
||||
|
}) |
||||
@ -0,0 +1,196 @@ |
|||||
|
const authorizedMiddleware = require("../authorized") |
||||
|
const env = require("../../environment") |
||||
|
const apiKey = require("../../utilities/security/apikey") |
||||
|
const { AuthTypes } = require("../../constants") |
||||
|
const { PermissionTypes, PermissionLevels } = require("../../utilities/security/permissions") |
||||
|
const { Test } = require("supertest") |
||||
|
jest.mock("../../environment") |
||||
|
jest.mock("../../utilities/security/apikey") |
||||
|
|
||||
|
class TestConfiguration { |
||||
|
constructor(role) { |
||||
|
this.middleware = authorizedMiddleware(role) |
||||
|
this.next = jest.fn() |
||||
|
this.throw = jest.fn() |
||||
|
this.ctx = { |
||||
|
headers: {}, |
||||
|
request: { |
||||
|
url: "" |
||||
|
}, |
||||
|
auth: {}, |
||||
|
next: this.next, |
||||
|
throw: this.throw |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
executeMiddleware() { |
||||
|
return this.middleware(this.ctx, this.next) |
||||
|
} |
||||
|
|
||||
|
setUser(user) { |
||||
|
this.ctx.user = user |
||||
|
} |
||||
|
|
||||
|
setMiddlewareRequiredPermission(...perms) { |
||||
|
this.middleware = authorizedMiddleware(...perms) |
||||
|
} |
||||
|
|
||||
|
setResourceId(id) { |
||||
|
this.ctx.resourceId = id |
||||
|
} |
||||
|
|
||||
|
setAuthenticated(isAuthed) { |
||||
|
this.ctx.auth = { authenticated: isAuthed } |
||||
|
} |
||||
|
|
||||
|
setRequestUrl(url) { |
||||
|
this.ctx.request.url = url |
||||
|
} |
||||
|
|
||||
|
setCloudEnv(isCloud) { |
||||
|
env.CLOUD = isCloud |
||||
|
} |
||||
|
|
||||
|
setRequestHeaders(headers) { |
||||
|
this.ctx.headers = headers |
||||
|
} |
||||
|
|
||||
|
afterEach() { |
||||
|
jest.clearAllMocks() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
describe("Authorization middleware", () => { |
||||
|
const next = jest.fn() |
||||
|
let config |
||||
|
|
||||
|
afterEach(() => { |
||||
|
config.afterEach() |
||||
|
}) |
||||
|
|
||||
|
beforeEach(() => { |
||||
|
config = new TestConfiguration() |
||||
|
}) |
||||
|
|
||||
|
it("passes the middleware for local webhooks", async () => { |
||||
|
config.setRequestUrl("https://something/webhooks/trigger") |
||||
|
await config.executeMiddleware() |
||||
|
expect(config.next).toHaveBeenCalled() |
||||
|
}) |
||||
|
|
||||
|
describe("external web hook call", () => { |
||||
|
let ctx = {} |
||||
|
let middleware |
||||
|
|
||||
|
beforeEach(() => { |
||||
|
config = new TestConfiguration() |
||||
|
config.setCloudEnv(true) |
||||
|
config.setRequestHeaders({ |
||||
|
"x-api-key": "abc123", |
||||
|
"x-instanceid": "instance123", |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
it("passes to next() if api key is valid", async () => { |
||||
|
apiKey.isAPIKeyValid.mockResolvedValueOnce(true) |
||||
|
|
||||
|
await config.executeMiddleware() |
||||
|
|
||||
|
expect(config.next).toHaveBeenCalled() |
||||
|
expect(config.ctx.auth).toEqual({ |
||||
|
authenticated: AuthTypes.EXTERNAL, |
||||
|
apiKey: config.ctx.headers["x-api-key"], |
||||
|
}) |
||||
|
expect(config.ctx.user).toEqual({ |
||||
|
appId: config.ctx.headers["x-instanceid"], |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
it("throws if api key is invalid", async () => { |
||||
|
apiKey.isAPIKeyValid.mockResolvedValueOnce(false) |
||||
|
|
||||
|
await config.executeMiddleware() |
||||
|
|
||||
|
expect(config.throw).toHaveBeenCalledWith(403, "API key invalid") |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe("non-webhook call", () => { |
||||
|
let config |
||||
|
|
||||
|
beforeEach(() => { |
||||
|
config = new TestConfiguration() |
||||
|
config.setCloudEnv(true) |
||||
|
config.setAuthenticated(true) |
||||
|
}) |
||||
|
|
||||
|
it("throws when no user data is present in context", async () => { |
||||
|
await config.executeMiddleware() |
||||
|
|
||||
|
expect(config.throw).toHaveBeenCalledWith(403, "No user info found") |
||||
|
}) |
||||
|
|
||||
|
it("passes on to next() middleware if user is an admin", async () => { |
||||
|
config.setUser({ |
||||
|
role: { |
||||
|
_id: "ADMIN", |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
await config.executeMiddleware() |
||||
|
|
||||
|
expect(config.next).toHaveBeenCalled() |
||||
|
}) |
||||
|
|
||||
|
it("throws if the user has only builder permissions", async () => { |
||||
|
config.setCloudEnv(false) |
||||
|
config.setMiddlewareRequiredPermission(PermissionTypes.BUILDER) |
||||
|
config.setUser({ |
||||
|
role: { |
||||
|
_id: "" |
||||
|
} |
||||
|
}) |
||||
|
await config.executeMiddleware() |
||||
|
|
||||
|
expect(config.throw).toHaveBeenCalledWith(403, "Not Authorized") |
||||
|
}) |
||||
|
|
||||
|
it("passes on to next() middleware if the user has resource permission", async () => { |
||||
|
config.setResourceId(PermissionTypes.QUERY) |
||||
|
config.setUser({ |
||||
|
role: { |
||||
|
_id: "" |
||||
|
} |
||||
|
}) |
||||
|
config.setMiddlewareRequiredPermission(PermissionTypes.QUERY) |
||||
|
|
||||
|
await config.executeMiddleware() |
||||
|
expect(config.next).toHaveBeenCalled() |
||||
|
}) |
||||
|
|
||||
|
it("throws if the user session is not authenticated after permission checks", async () => { |
||||
|
config.setUser({ |
||||
|
role: { |
||||
|
_id: "" |
||||
|
}, |
||||
|
}) |
||||
|
config.setAuthenticated(false) |
||||
|
|
||||
|
await config.executeMiddleware() |
||||
|
expect(config.throw).toHaveBeenCalledWith(403, "Session not authenticated") |
||||
|
}) |
||||
|
|
||||
|
it("throws if the user does not have base permissions to perform the operation", async () => { |
||||
|
config.setUser({ |
||||
|
role: { |
||||
|
_id: "" |
||||
|
}, |
||||
|
}) |
||||
|
config.setMiddlewareRequiredPermission(PermissionTypes.ADMIN, PermissionLevels.BASIC) |
||||
|
|
||||
|
await config.executeMiddleware() |
||||
|
expect(config.throw).toHaveBeenCalledWith(403, "User does not have permission") |
||||
|
}) |
||||
|
}) |
||||
|
}) |
||||
@ -0,0 +1,105 @@ |
|||||
|
const { |
||||
|
paramResource, |
||||
|
paramSubResource, |
||||
|
bodyResource, |
||||
|
bodySubResource, |
||||
|
ResourceIdGetter |
||||
|
} = require("../resourceId") |
||||
|
|
||||
|
class TestConfiguration { |
||||
|
constructor(middleware) { |
||||
|
this.middleware = middleware |
||||
|
this.ctx = { |
||||
|
request: {}, |
||||
|
} |
||||
|
this.next = jest.fn() |
||||
|
} |
||||
|
|
||||
|
setParams(params) { |
||||
|
this.ctx.params = params |
||||
|
} |
||||
|
|
||||
|
setBody(body) { |
||||
|
this.ctx.body = body |
||||
|
} |
||||
|
|
||||
|
executeMiddleware() { |
||||
|
return this.middleware(this.ctx, this.next) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
describe("resourceId middleware", () => { |
||||
|
it("calls next() when there is no request object to parse", () => { |
||||
|
const config = new TestConfiguration(paramResource("main")) |
||||
|
|
||||
|
config.executeMiddleware() |
||||
|
|
||||
|
expect(config.next).toHaveBeenCalled() |
||||
|
expect(config.ctx.resourceId).toBeUndefined() |
||||
|
}) |
||||
|
|
||||
|
it("generates a resourceId middleware for context query parameters", () => { |
||||
|
const config = new TestConfiguration(paramResource("main")) |
||||
|
config.setParams({ |
||||
|
main: "test" |
||||
|
}) |
||||
|
|
||||
|
config.executeMiddleware() |
||||
|
|
||||
|
expect(config.ctx.resourceId).toEqual("test") |
||||
|
}) |
||||
|
|
||||
|
it("generates a resourceId middleware for context query sub parameters", () => { |
||||
|
const config = new TestConfiguration(paramSubResource("main", "sub")) |
||||
|
config.setParams({ |
||||
|
main: "main", |
||||
|
sub: "test" |
||||
|
}) |
||||
|
|
||||
|
config.executeMiddleware() |
||||
|
|
||||
|
expect(config.ctx.resourceId).toEqual("main") |
||||
|
expect(config.ctx.subResourceId).toEqual("test") |
||||
|
}) |
||||
|
|
||||
|
it("generates a resourceId middleware for context request body", () => { |
||||
|
const config = new TestConfiguration(bodyResource("main")) |
||||
|
config.setBody({ |
||||
|
main: "test" |
||||
|
}) |
||||
|
|
||||
|
config.executeMiddleware() |
||||
|
|
||||
|
expect(config.ctx.resourceId).toEqual("test") |
||||
|
}) |
||||
|
|
||||
|
it("generates a resourceId middleware for context request body sub fields", () => { |
||||
|
const config = new TestConfiguration(bodySubResource("main", "sub")) |
||||
|
config.setBody({ |
||||
|
main: "main", |
||||
|
sub: "test" |
||||
|
}) |
||||
|
|
||||
|
config.executeMiddleware() |
||||
|
|
||||
|
expect(config.ctx.resourceId).toEqual("main") |
||||
|
expect(config.ctx.subResourceId).toEqual("test") |
||||
|
}) |
||||
|
|
||||
|
it("parses resourceIds correctly for custom middlewares", () => { |
||||
|
const middleware = new ResourceIdGetter("body") |
||||
|
.mainResource("custom") |
||||
|
.subResource("customSub") |
||||
|
.build() |
||||
|
config = new TestConfiguration(middleware) |
||||
|
config.setBody({ |
||||
|
custom: "test", |
||||
|
customSub: "subtest" |
||||
|
}) |
||||
|
|
||||
|
config.executeMiddleware() |
||||
|
|
||||
|
expect(config.ctx.resourceId).toEqual("test") |
||||
|
expect(config.ctx.subResourceId).toEqual("subtest") |
||||
|
}) |
||||
|
}) |
||||
@ -0,0 +1,75 @@ |
|||||
|
const selfHostMiddleware = require("../selfhost"); |
||||
|
const env = require("../../environment") |
||||
|
const hosting = require("../../utilities/builder/hosting"); |
||||
|
jest.mock("../../environment") |
||||
|
jest.mock("../../utilities/builder/hosting") |
||||
|
|
||||
|
class TestConfiguration { |
||||
|
constructor() { |
||||
|
this.next = jest.fn() |
||||
|
this.throw = jest.fn() |
||||
|
this.middleware = selfHostMiddleware |
||||
|
|
||||
|
this.ctx = { |
||||
|
next: this.next, |
||||
|
throw: this.throw |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
executeMiddleware() { |
||||
|
return this.middleware(this.ctx, this.next) |
||||
|
} |
||||
|
|
||||
|
setCloudHosted() { |
||||
|
env.CLOUD = 1 |
||||
|
env.SELF_HOSTED = 0 |
||||
|
} |
||||
|
|
||||
|
setSelfHosted() { |
||||
|
env.CLOUD = 0 |
||||
|
env.SELF_HOSTED = 1 |
||||
|
} |
||||
|
|
||||
|
afterEach() { |
||||
|
jest.clearAllMocks() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
describe("Self host middleware", () => { |
||||
|
let config |
||||
|
|
||||
|
beforeEach(() => { |
||||
|
config = new TestConfiguration() |
||||
|
}) |
||||
|
|
||||
|
afterEach(() => { |
||||
|
config.afterEach() |
||||
|
}) |
||||
|
|
||||
|
it("calls next() when CLOUD and SELF_HOSTED env vars are set", async () => { |
||||
|
env.CLOUD = 1 |
||||
|
env.SELF_HOSTED = 1 |
||||
|
|
||||
|
await config.executeMiddleware() |
||||
|
expect(config.next).toHaveBeenCalled() |
||||
|
}) |
||||
|
|
||||
|
it("throws when hostingInfo type is cloud", async () => { |
||||
|
config.setSelfHosted() |
||||
|
|
||||
|
hosting.getHostingInfo.mockImplementationOnce(() => ({ type: hosting.HostingTypes.CLOUD })) |
||||
|
|
||||
|
await config.executeMiddleware() |
||||
|
expect(config.throw).toHaveBeenCalledWith(400, "Endpoint unavailable in cloud hosting.") |
||||
|
expect(config.next).not.toHaveBeenCalled() |
||||
|
}) |
||||
|
|
||||
|
it("calls the self hosting middleware to pass through to next() when the hostingInfo type is self", async () => { |
||||
|
config.setSelfHosted() |
||||
|
|
||||
|
hosting.getHostingInfo.mockImplementationOnce(() => ({ type: hosting.HostingTypes.SELF })) |
||||
|
|
||||
|
await config.executeMiddleware() |
||||
|
expect(config.next).toHaveBeenCalled() |
||||
|
}) |
||||
|
}) |
||||
@ -0,0 +1,129 @@ |
|||||
|
const usageQuotaMiddleware = require("../usageQuota") |
||||
|
const usageQuota = require("../../utilities/usageQuota") |
||||
|
const CouchDB = require("../../db") |
||||
|
const env = require("../../environment") |
||||
|
|
||||
|
jest.mock("../../db"); |
||||
|
jest.mock("../../utilities/usageQuota") |
||||
|
jest.mock("../../environment") |
||||
|
|
||||
|
class TestConfiguration { |
||||
|
constructor() { |
||||
|
this.throw = jest.fn() |
||||
|
this.next = jest.fn() |
||||
|
this.middleware = usageQuotaMiddleware |
||||
|
this.ctx = { |
||||
|
throw: this.throw, |
||||
|
next: this.next, |
||||
|
user: { |
||||
|
appId: "test" |
||||
|
}, |
||||
|
request: { |
||||
|
body: {} |
||||
|
}, |
||||
|
req: { |
||||
|
method: "POST", |
||||
|
url: "/rows" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
executeMiddleware() { |
||||
|
return this.middleware(this.ctx, this.next) |
||||
|
} |
||||
|
|
||||
|
cloudHosted(bool) { |
||||
|
if (bool) { |
||||
|
env.CLOUD = 1 |
||||
|
this.ctx.auth = { apiKey: "test" } |
||||
|
} else { |
||||
|
env.CLOUD = 0 |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
setMethod(method) { |
||||
|
this.ctx.req.method = method |
||||
|
} |
||||
|
|
||||
|
setUrl(url) { |
||||
|
this.ctx.req.url = url |
||||
|
} |
||||
|
|
||||
|
setBody(body) { |
||||
|
this.ctx.request.body = body |
||||
|
} |
||||
|
|
||||
|
setFiles(files) { |
||||
|
this.ctx.request.files = { file: files } |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
describe("usageQuota middleware", () => { |
||||
|
let config |
||||
|
|
||||
|
beforeEach(() => { |
||||
|
config = new TestConfiguration() |
||||
|
}) |
||||
|
|
||||
|
it("skips the middleware if there is no usage property or method", async () => { |
||||
|
await config.executeMiddleware() |
||||
|
expect(config.next).toHaveBeenCalled() |
||||
|
}) |
||||
|
|
||||
|
it("passes through to next middleware if document already exists", async () => { |
||||
|
config.setBody({ |
||||
|
_id: "test" |
||||
|
}) |
||||
|
|
||||
|
CouchDB.mockImplementationOnce(() => ({ |
||||
|
get: async () => true |
||||
|
})) |
||||
|
|
||||
|
await config.executeMiddleware() |
||||
|
|
||||
|
expect(config.next).toHaveBeenCalled() |
||||
|
expect(config.ctx.preExisting).toBe(true) |
||||
|
}) |
||||
|
|
||||
|
it("throws if request has _id, but the document no longer exists", async () => { |
||||
|
config.setBody({ |
||||
|
_id: "123" |
||||
|
}) |
||||
|
|
||||
|
CouchDB.mockImplementationOnce(() => ({ |
||||
|
get: async () => { |
||||
|
throw new Error() |
||||
|
} |
||||
|
})) |
||||
|
|
||||
|
await config.executeMiddleware() |
||||
|
expect(config.throw).toHaveBeenCalledWith(404, `${config.ctx.request.body._id} does not exist`) |
||||
|
}) |
||||
|
|
||||
|
it("calculates and persists the correct usage quota for the relevant action", async () => { |
||||
|
config.setUrl("/rows") |
||||
|
config.cloudHosted(true) |
||||
|
|
||||
|
await config.executeMiddleware() |
||||
|
|
||||
|
expect(usageQuota.update).toHaveBeenCalledWith("test", "rows", 1) |
||||
|
expect(config.next).toHaveBeenCalled() |
||||
|
}) |
||||
|
|
||||
|
it("calculates the correct file size from a file upload call and adds it to quota", async () => { |
||||
|
config.setUrl("/upload") |
||||
|
config.cloudHosted(true) |
||||
|
config.setFiles([ |
||||
|
{ |
||||
|
size: 100 |
||||
|
}, |
||||
|
{ |
||||
|
size: 10000 |
||||
|
}, |
||||
|
]) |
||||
|
await config.executeMiddleware() |
||||
|
|
||||
|
expect(usageQuota.update).toHaveBeenCalledWith("test", "storage", 10100) |
||||
|
expect(config.next).toHaveBeenCalled() |
||||
|
}) |
||||
|
}) |
||||
Loading…
Reference in new issue