1
0
Fork 0
mirror of synced 2024-07-08 07:46:10 +12:00

Merge pull request #11278 from Budibase/account-api-tests

Account Portal API Tests - Accounts Endpoint
This commit is contained in:
Rory Powell 2023-07-20 11:54:04 +01:00 committed by GitHub
commit 740b1ab97d
27 changed files with 553 additions and 116 deletions

View file

@ -159,7 +159,7 @@ jobs:
run: |
cd qa-core
yarn setup
yarn test:ci
yarn serve:test:self:ci
env:
BB_ADMIN_USER_EMAIL: admin
BB_ADMIN_USER_PASSWORD: admin

View file

@ -20,6 +20,8 @@ export enum Header {
TYPE = "x-budibase-type",
PREVIEW_ROLE = "x-budibase-role",
TENANT_ID = "x-budibase-tenant-id",
VERIFICATION_CODE = "x-budibase-verification-code",
RETURN_VERIFICATION_CODE = "x-budibase-return-verification-code",
TOKEN = "x-budibase-token",
CSRF_TOKEN = "x-csrf-token",
CORRELATION_ID = "x-budibase-correlation-id",

View file

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

View file

@ -2,11 +2,9 @@ import pino, { LoggerOptions } from "pino"
import pinoPretty from "pino-pretty"
import { IdentityType } from "@budibase/types"
import env from "../../environment"
import * as context from "../../context"
import * as correlation from "../correlation"
import { LOG_CONTEXT } from "../index"
import { localFileDestination } from "../system"
@ -93,15 +91,13 @@ if (!env.DISABLE_PINO_LOGGER) {
let contextObject = {}
if (LOG_CONTEXT) {
contextObject = {
tenantId: getTenantId(),
appId: getAppId(),
automationId: getAutomationId(),
identityId: identity?._id,
identityType: identity?.type,
correlationId: correlation.getId(),
}
contextObject = {
tenantId: getTenantId(),
appId: getAppId(),
automationId: getAutomationId(),
identityId: identity?._id,
identityType: identity?.type,
correlationId: correlation.getId(),
}
const mergingObject: any = {

View file

@ -1,3 +1,4 @@
import { Account } from "../../documents"
import { Hosting } from "../../sdk"
export interface CreateAccountRequest {
@ -11,3 +12,11 @@ export interface CreateAccountRequest {
name?: string
password: string
}
export interface SearchAccountsRequest {
// one or the other - not both
email?: string
tenantId?: string
}
export type SearchAccountsResponse = Account[]

View file

@ -5,6 +5,9 @@ const config: Config.InitialOptions = {
setupFiles: ["./src/jest/jestSetup.ts"],
setupFilesAfterEnv: ["./src/jest/jest.extends.ts"],
testEnvironment: "node",
transform: {
"^.+\\.ts?$": "@swc/jest",
},
globalSetup: "./src/jest/globalSetup.ts",
globalTeardown: "./src/jest/globalTeardown.ts",
moduleNameMapper: {

View file

@ -15,8 +15,10 @@
"test:watch": "yarn run test --watch",
"test:debug": "DEBUG=1 yarn run test",
"test:notify": "node scripts/testResultsWebhook",
"test:smoke": "yarn run test --testPathIgnorePatterns=/.+\\.integration\\.spec\\.ts",
"test:ci": "start-server-and-test dev:built http://localhost:4001/health test:smoke",
"test:cloud:prod": "yarn run test --testPathIgnorePatterns=\\.integration\\.",
"test:cloud:qa": "yarn run test",
"test:self:ci": "yarn run test --testPathIgnorePatterns=\\.integration\\. \\.cloud\\.",
"serve:test:self:ci": "start-server-and-test dev:built http://localhost:4001/health test:self:ci",
"serve": "start-server-and-test dev:built http://localhost:4001/health",
"dev:built": "cd ../ && yarn dev:built"
},
@ -30,6 +32,8 @@
"jest": "29.0.0",
"prettier": "2.7.1",
"start-server-and-test": "1.14.0",
"@swc/core": "^1.3.25",
"@swc/jest": "^0.2.24",
"timekeeper": "2.2.0",
"ts-jest": "29.0.0",
"ts-node": "10.8.1",

View file

@ -12,6 +12,8 @@ function init() {
BB_ADMIN_USER_EMAIL: "admin",
BB_ADMIN_USER_PASSWORD: "admin",
LOG_LEVEL: "info",
JEST_TIMEOUT: "60000",
DISABLE_PINO_LOGGER: "1",
}
let envFile = ""
Object.keys(envFileJson).forEach(key => {

View file

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

View file

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

View file

@ -1,75 +1,117 @@
import { Response } from "node-fetch"
import { Account, CreateAccountRequest } from "@budibase/types"
import {
Account,
CreateAccountRequest,
SearchAccountsRequest,
SearchAccountsResponse,
} from "@budibase/types"
import AccountInternalAPIClient from "../AccountInternalAPIClient"
import { APIRequestOpts } from "../../../types"
import { Header } from "@budibase/backend-core"
import BaseAPI from "./BaseAPI"
export default class AccountAPI {
export default class AccountAPI extends BaseAPI {
client: AccountInternalAPIClient
constructor(client: AccountInternalAPIClient) {
super()
this.client = client
}
async validateEmail(
email: string,
opts: APIRequestOpts = { doExpect: true }
): Promise<Response> {
const [response, json] = await this.client.post(
`/api/accounts/validate/email`,
{
async validateEmail(email: string, opts: APIRequestOpts = { status: 200 }) {
return this.doRequest(() => {
return this.client.post(`/api/accounts/validate/email`, {
body: { email },
}
)
if (opts.doExpect) {
expect(response).toHaveStatusCode(200)
}
return response
})
}, opts)
}
async validateTenantId(
tenantId: string,
opts: APIRequestOpts = { doExpect: true }
): Promise<Response> {
const [response, json] = await this.client.post(
`/api/accounts/validate/tenantId`,
{
opts: APIRequestOpts = { status: 200 }
) {
return this.doRequest(() => {
return this.client.post(`/api/accounts/validate/tenantId`, {
body: { tenantId },
}
)
if (opts.doExpect) {
expect(response).toHaveStatusCode(200)
}
return response
})
}, opts)
}
async create(
body: CreateAccountRequest,
opts: APIRequestOpts = { doExpect: true }
opts: APIRequestOpts & { autoVerify: boolean } = {
status: 201,
autoVerify: false,
}
): 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]
return this.doRequest(() => {
const headers = {
"no-verify": opts.autoVerify ? "1" : "0",
}
return this.client.post(`/api/accounts`, {
body,
headers,
})
}, opts)
}
async delete(accountID: string) {
const [response, json] = await this.client.del(
`/api/accounts/${accountID}`,
{
async delete(accountID: string, opts: APIRequestOpts = { status: 204 }) {
return this.doRequest(() => {
return this.client.del(`/api/accounts/${accountID}`, {
internal: true,
})
}, opts)
}
async deleteCurrentAccount(opts: APIRequestOpts = { status: 204 }) {
return this.doRequest(() => {
return this.client.del(`/api/accounts`)
}, opts)
}
async verifyAccount(
verificationCode: string,
opts: APIRequestOpts = { status: 200 }
) {
return this.doRequest(() => {
return this.client.post(`/api/accounts/verify`, {
body: { verificationCode },
})
}, opts)
}
async sendVerificationEmail(
email: string,
opts: APIRequestOpts = { status: 200 }
): Promise<[Response, string]> {
return this.doRequest(async () => {
const [response] = await this.client.post(`/api/accounts/verify/send`, {
body: { email },
headers: {
[Header.RETURN_VERIFICATION_CODE]: "1",
},
})
const code = response.headers.get(Header.VERIFICATION_CODE)
return [response, code]
}, opts)
}
async search(
searchType: string,
search: "email" | "tenantId",
opts: APIRequestOpts = { status: 200 }
): Promise<[Response, SearchAccountsResponse]> {
return this.doRequest(() => {
let body: SearchAccountsRequest = {}
if (search === "email") {
body.email = searchType
} else if (search === "tenantId") {
body.tenantId = searchType
}
)
// can't use expect here due to use in global teardown
if (response.status !== 204) {
throw new Error(`Could not delete accountId=${accountID}`)
}
return response
return this.client.post(`/api/accounts/search`, {
body,
internal: true,
})
}, opts)
}
}

View file

@ -0,0 +1,30 @@
import { Response } from "node-fetch"
import AccountInternalAPIClient from "../AccountInternalAPIClient"
import { APIRequestOpts } from "../../../types"
import BaseAPI from "./BaseAPI"
export default class AuthAPI extends BaseAPI {
client: AccountInternalAPIClient
constructor(client: AccountInternalAPIClient) {
super()
this.client = client
}
async login(
email: string,
password: string,
opts: APIRequestOpts = { doExpect: true, status: 200 }
): Promise<[Response, string]> {
return this.doRequest(async () => {
const [res] = await this.client.post(`/api/auth/login`, {
body: {
email: email,
password: password,
},
})
const cookie = res.headers.get("set-cookie")
return [res, cookie]
}, opts)
}
}

View file

@ -0,0 +1,20 @@
import { Response } from "node-fetch"
import { APIRequestOpts } from "../../../types"
export default class BaseAPI {
async doRequest(
request: () => Promise<[Response, any]>,
opts: APIRequestOpts
): Promise<[Response, any]> {
const [response, body] = await request()
// do expect on by default
if (opts.doExpect === undefined) {
opts.doExpect = true
}
if (opts.doExpect && opts.status) {
expect(response).toHaveStatusCode(opts.status)
}
return [response, body]
}
}

View file

@ -1,31 +1,27 @@
import AccountInternalAPIClient from "../AccountInternalAPIClient"
import { Account, UpdateLicenseRequest } from "@budibase/types"
import { Response } from "node-fetch"
import BaseAPI from "./BaseAPI"
import { APIRequestOpts } from "../../../types"
export default class LicenseAPI {
export default class LicenseAPI extends BaseAPI {
client: AccountInternalAPIClient
constructor(client: AccountInternalAPIClient) {
super()
this.client = client
}
async updateLicense(
accountId: string,
body: UpdateLicenseRequest
body: UpdateLicenseRequest,
opts: APIRequestOpts = { status: 200 }
): Promise<[Response, Account]> {
const [response, json] = await this.client.put(
`/api/accounts/${accountId}/license`,
{
return this.doRequest(() => {
return this.client.put(`/api/accounts/${accountId}/license`, {
body,
internal: true,
}
)
if (response.status !== 200) {
throw new Error(
`Could not update license for accountId=${accountId}: ${response.status}`
)
}
return [response, json]
})
}, opts)
}
}

View file

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

View file

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

View file

@ -0,0 +1,21 @@
import { generator } from "../../shared"
import { Hosting, CreateAccountRequest } from "@budibase/types"
// TODO: Refactor me to central location
export const generateAccount = (): CreateAccountRequest => {
const uuid = generator.guid()
const email = `${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

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

View file

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

View file

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

View file

@ -1,5 +1,4 @@
process.env.DISABLE_PINO_LOGGER = "1"
import { DEFAULT_TENANT_ID, logging } from "@budibase/backend-core"
import { DEFAULT_TENANT_ID } from "@budibase/backend-core"
import { AccountInternalAPI } from "../account-api"
import * as fixtures from "../internal-api/fixtures"
import { BudibaseInternalAPI } from "../internal-api"
@ -7,10 +6,6 @@ import { Account, CreateAccountRequest, Feature } 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({})
@ -23,7 +18,10 @@ async function createAccount(): Promise<[CreateAccountRequest, Account]> {
const account = fixtures.accounts.generateAccount()
await accountsApi.accounts.validateEmail(account.email, API_OPTS)
await accountsApi.accounts.validateTenantId(account.tenantId, API_OPTS)
const [res, newAccount] = await accountsApi.accounts.create(account, API_OPTS)
const [res, newAccount] = await accountsApi.accounts.create(account, {
...API_OPTS,
autoVerify: true,
})
await updateLicense(newAccount.accountId)
return [account, newAccount]
}
@ -31,25 +29,34 @@ async function createAccount(): Promise<[CreateAccountRequest, Account]> {
const UNLIMITED = { value: -1 }
async function updateLicense(accountId: string) {
await accountsApi.licenses.updateLicense(accountId, {
overrides: {
// add all features
features: Object.values(Feature),
quotas: {
usage: {
monthly: {
automations: UNLIMITED,
},
static: {
rows: UNLIMITED,
users: UNLIMITED,
userGroups: UNLIMITED,
plugins: UNLIMITED,
const [response] = await accountsApi.licenses.updateLicense(
accountId,
{
overrides: {
// add all features
features: Object.values(Feature),
quotas: {
usage: {
monthly: {
automations: UNLIMITED,
},
static: {
rows: UNLIMITED,
users: UNLIMITED,
userGroups: UNLIMITED,
plugins: UNLIMITED,
},
},
},
},
},
})
{ doExpect: false }
)
if (response.status !== 200) {
throw new Error(
`Could not update license for accountId=${accountId}: ${response.status}`
)
}
}
async function loginAsAdmin() {
@ -68,8 +75,7 @@ async function loginAsAdmin() {
}
async function loginAsAccount(account: CreateAccountRequest) {
const [res, cookie] = await internalApi.auth.login(
account.tenantId,
const [res, cookie] = await accountsApi.auth.login(
account.email,
account.password,
API_OPTS
@ -90,6 +96,8 @@ async function setup() {
// @ts-ignore
global.qa.tenantId = account.tenantId
// @ts-ignore
global.qa.email = account.email
// @ts-ignore
global.qa.accountId = newAccount.accountId
await loginAsAccount(account)
} else {

View file

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

View file

@ -1,5 +1,3 @@
import { logging } from "@budibase/backend-core"
logging.LOG_CONTEXT = false
jest.retryTimes(2)
jest.setTimeout(60000)
const envTimeout = process.env.JEST_TIMEOUT
const timeout = envTimeout && parseInt(envTimeout)
jest.setTimeout(timeout || 60000)

View file

@ -1,7 +1,8 @@
import { BudibaseInternalAPI } from "../internal-api"
import { AccountInternalAPI } from "../account-api"
import { CreateAppRequest, State } from "../types"
import { APIRequestOpts, CreateAppRequest, State } from "../types"
import * as fixtures from "../internal-api/fixtures"
import { CreateAccountRequest } from "@budibase/types"
export default class BudibaseTestConfiguration {
// apis
@ -23,6 +24,8 @@ export default class BudibaseTestConfiguration {
// @ts-ignore
this.state.tenantId = global.qa.tenantId
// @ts-ignore
this.state.email = global.qa.email
// @ts-ignore
this.state.cookie = global.qa.authCookie
}
@ -40,10 +43,49 @@ export default class BudibaseTestConfiguration {
// AUTH
async doInNewState(task: () => Promise<any>) {
return this.doWithState(task, {})
}
async doWithState(task: () => Promise<any>, state: State) {
const original = { ...this.state }
// override the state
this.state.apiKey = state.apiKey
this.state.appId = state.appId
this.state.cookie = state.cookie
this.state.tableId = state.tableId
this.state.tenantId = state.tenantId
this.state.email = state.email
await task()
// restore the state
this.state.apiKey = original.apiKey
this.state.appId = original.appId
this.state.cookie = original.cookie
this.state.tableId = original.tableId
this.state.tenantId = original.tenantId
this.state.email = original.email
}
async loginAsAccount(
account: CreateAccountRequest,
opts: APIRequestOpts = {}
) {
const [_, cookie] = await this.accountsApi.auth.login(
account.email,
account.password,
opts
)
this.state.cookie = cookie
}
async login(email: string, password: string, tenantId?: string) {
if (!tenantId && this.state.tenantId) {
tenantId = this.state.tenantId
} else {
}
if (!tenantId) {
throw new Error("Could not determine tenant id")
}
const [res, cookie] = await this.internalApi.auth.login(

View file

@ -1,5 +1,6 @@
export interface APIRequestOpts {
// in some cases we need to bypass the expect assertion in an api call
// e.g. during global setup where jest is not available
doExpect: boolean
doExpect?: boolean
status?: number
}

View file

@ -4,4 +4,5 @@ export interface State {
cookie?: string
tableId?: string
tenantId?: string
email?: string
}

View file

@ -455,6 +455,13 @@
slash "^3.0.0"
strip-ansi "^6.0.0"
"@jest/create-cache-key-function@^27.4.2":
version "27.5.1"
resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-27.5.1.tgz#7448fae15602ea95c828f5eceed35c202a820b31"
integrity sha512-dmH1yW+makpTSURTy8VzdUwFnfQh1G8R+DxO2Ho2FFmBbKFEVm+3jWdvFhE2VqB/LATCTokkP0dotjyQyw5/AQ==
dependencies:
"@jest/types" "^27.5.1"
"@jest/environment@^29.5.0":
version "29.5.0"
resolved "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz"
@ -589,6 +596,17 @@
slash "^3.0.0"
write-file-atomic "^4.0.2"
"@jest/types@^27.5.1":
version "27.5.1"
resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80"
integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==
dependencies:
"@types/istanbul-lib-coverage" "^2.0.0"
"@types/istanbul-reports" "^3.0.0"
"@types/node" "*"
"@types/yargs" "^16.0.0"
chalk "^4.0.0"
"@jest/types@^29.0.0", "@jest/types@^29.5.0":
version "29.5.0"
resolved "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz"
@ -738,6 +756,80 @@
dependencies:
"@sinonjs/commons" "^2.0.0"
"@swc/core-darwin-arm64@1.3.70":
version "1.3.70"
resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.70.tgz#056ac6899e22cb7f7be21388d4d938ca5123a72b"
integrity sha512-31+mcl0dgdRHvZRjhLOK9V6B+qJ7nxDZYINr9pBlqGWxknz37Vld5KK19Kpr79r0dXUZvaaelLjCnJk9dA2PcQ==
"@swc/core-darwin-x64@1.3.70":
version "1.3.70"
resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.3.70.tgz#3945814de6fadbee5b46cb2a3422353acb420c5c"
integrity sha512-GMFJ65E18zQC80t0os+TZvI+8lbRuitncWVge/RXmXbVLPRcdykP4EJ87cqzcG5Ah0z18/E0T+ixD6jHRisrYQ==
"@swc/core-linux-arm-gnueabihf@1.3.70":
version "1.3.70"
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.70.tgz#7960e54ede1af75a7ef99ee53febf37fea6269a8"
integrity sha512-wjhCwS8LCiAq2VedF1b4Bryyw68xZnfMED4pLRazAl8BaUlDFANfRBORNunxlfHQj4V3x39IaiLgCZRHMdzXBg==
"@swc/core-linux-arm64-gnu@1.3.70":
version "1.3.70"
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.70.tgz#df9654e5040bbeb1619739756a7f50100e38ace8"
integrity sha512-9D/Rx67cAOnMiexvCqARxvhj7coRajTp5HlJHuf+rfwMqI2hLhpO9/pBMQxBUAWxODO/ksQ/OF+GJRjmtWw/2A==
"@swc/core-linux-arm64-musl@1.3.70":
version "1.3.70"
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.70.tgz#2c2aab5a136c7eb409ddc9cdc4f947a68fd74493"
integrity sha512-gkjxBio7XD+1GlQVVyPP/qeFkLu83VhRHXaUrkNYpr5UZG9zZurBERT9nkS6Y+ouYh+Q9xmw57aIyd2KvD2zqQ==
"@swc/core-linux-x64-gnu@1.3.70":
version "1.3.70"
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.70.tgz#774351532b154ed36a5c6d14b647e7a8ab510028"
integrity sha512-/nCly+V4xfMVwfEUoLLAukxUSot/RcSzsf6GdsGTjFcrp5sZIntAjokYRytm3VT1c2TK321AfBorsi9R5w8Y7Q==
"@swc/core-linux-x64-musl@1.3.70":
version "1.3.70"
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.70.tgz#c0b1b4ad5f4ef187eaa093589a4933ecb6836546"
integrity sha512-HoOsPJbt361KGKaivAK0qIiYARkhzlxeAfvF5NlnKxkIMOZpQ46Lwj3tR0VWohKbrhS+cYKFlVuDi5XnDkx0XA==
"@swc/core-win32-arm64-msvc@1.3.70":
version "1.3.70"
resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.70.tgz#8640267ce3959db0e7e682103677a5e0500b5ea7"
integrity sha512-hm4IBK/IaRil+aj1cWU6f0GyAdHpw/Jr5nyFYLM2c/tt7w2t5hgb8NjzM2iM84lOClrig1fG6edj2vCF1dFzNQ==
"@swc/core-win32-ia32-msvc@1.3.70":
version "1.3.70"
resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.70.tgz#f95d5656622f5a963bc0125da9fda84cf40faa8d"
integrity sha512-5cgKUKIT/9Fp5fCA+zIjYCQ4dSvjFYOeWGZR3QiTXGkC4bGa1Ji9SEPyeIAX0iruUnKjYaZB9RvHK2tNn7RLrQ==
"@swc/core-win32-x64-msvc@1.3.70":
version "1.3.70"
resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.70.tgz#5b3acddb96fdf60df089b837061915cb4be94eaa"
integrity sha512-LE8lW46+TQBzVkn2mHBlk8DIElPIZ2dO5P8AbJiARNBAnlqQWu67l9gWM89UiZ2l33J2cI37pHzON3tKnT8f9g==
"@swc/core@^1.3.25":
version "1.3.70"
resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.3.70.tgz#f5ddc6fe6add7a99f5b94d2214ad0d8527d11479"
integrity sha512-LWVWlEDLlOD25PvA2NEz41UzdwXnlDyBiZbe69s3zM0DfCPwZXLUm79uSqH9ItsOjTrXSL5/1+XUL6C/BZwChA==
optionalDependencies:
"@swc/core-darwin-arm64" "1.3.70"
"@swc/core-darwin-x64" "1.3.70"
"@swc/core-linux-arm-gnueabihf" "1.3.70"
"@swc/core-linux-arm64-gnu" "1.3.70"
"@swc/core-linux-arm64-musl" "1.3.70"
"@swc/core-linux-x64-gnu" "1.3.70"
"@swc/core-linux-x64-musl" "1.3.70"
"@swc/core-win32-arm64-msvc" "1.3.70"
"@swc/core-win32-ia32-msvc" "1.3.70"
"@swc/core-win32-x64-msvc" "1.3.70"
"@swc/jest@^0.2.24":
version "0.2.26"
resolved "https://registry.yarnpkg.com/@swc/jest/-/jest-0.2.26.tgz#6ef2d6d31869e3aaddc132603bc21f2e4c57cc5d"
integrity sha512-7lAi7q7ShTO3E5Gt1Xqf3pIhRbERxR1DUxvtVa9WKzIB+HGQ7wZP5sYx86zqnaEoKKGhmOoZ7gyW0IRu8Br5+A==
dependencies:
"@jest/create-cache-key-function" "^27.4.2"
jsonc-parser "^3.2.0"
"@techpass/passport-openidconnect@0.3.2":
version "0.3.2"
resolved "https://registry.npmjs.org/@techpass/passport-openidconnect/-/passport-openidconnect-0.3.2.tgz"
@ -885,6 +977,13 @@
resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz"
integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==
"@types/yargs@^16.0.0":
version "16.0.5"
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.5.tgz#12cc86393985735a283e387936398c2f9e5f88e3"
integrity sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==
dependencies:
"@types/yargs-parser" "*"
"@types/yargs@^17.0.8":
version "17.0.22"
resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.22.tgz"
@ -2866,6 +2965,11 @@ json5@^2.2.1, json5@^2.2.2:
resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
jsonc-parser@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76"
integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==
jsonwebtoken@9.0.0:
version "9.0.0"
resolved "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz"