chore(server): various useful multiregion changes from comments pr (#3499)
* chore(server): various useful multiregion changes from comments pr * hopefully fixing CI? * test fix * more test fixes?? * more test fixes.. * plz work ;(((( * revert multiregion on by default
This commit is contained in:
Родитель
1d1a54e082
Коммит
f961a6da81
|
@ -595,13 +595,13 @@ jobs:
|
|||
POSTGRES_DB: speckle2_test
|
||||
POSTGRES_PASSWORD: speckle
|
||||
POSTGRES_USER: speckle
|
||||
command: -c 'max_connections=1000'
|
||||
command: -c 'max_connections=1000' -c 'wal_level=logical'
|
||||
- image: 'speckle/speckle-postgres'
|
||||
environment:
|
||||
POSTGRES_DB: speckle2_test
|
||||
POSTGRES_PASSWORD: speckle
|
||||
POSTGRES_USER: speckle
|
||||
command: -c 'max_connections=1000' -c 'port=5433'
|
||||
command: -c 'max_connections=1000' -c 'port=5433' -c 'wal_level=logical'
|
||||
- image: 'minio/minio'
|
||||
command: server /data --console-address ":9001"
|
||||
environment:
|
||||
|
@ -623,8 +623,10 @@ jobs:
|
|||
S3_REGION: '' # optional, defaults to 'us-east-1'
|
||||
AUTOMATE_ENCRYPTION_KEYS_PATH: 'test/assets/automate/encryptionKeys.json'
|
||||
FF_BILLING_INTEGRATION_ENABLED: 'true'
|
||||
# These are the only 2 different env keys:
|
||||
# These are the only different env keys:
|
||||
MULTI_REGION_CONFIG_PATH: '../../.circleci/multiregion.test-ci.json'
|
||||
FF_WORKSPACES_MODULE_ENABLED: 'true'
|
||||
FF_WORKSPACES_MULTI_REGION_ENABLED: 'true'
|
||||
RUN_TESTS_IN_MULTIREGION_MODE: true
|
||||
|
||||
test-frontend-2:
|
||||
|
|
|
@ -89,11 +89,11 @@
|
|||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@datadog/datadog-ci": "^2.37.0",
|
||||
"@eslint/config-inspector": "^0.4.10",
|
||||
"@graphql-codegen/cli": "^5.0.2",
|
||||
"@graphql-codegen/client-preset": "^4.3.0",
|
||||
"@graphql-codegen/plugin-helpers": "^5.0.4",
|
||||
"@graphql-codegen/typescript": "^4.0.9",
|
||||
"@graphql-codegen/visitor-plugin-common": "5.3.1",
|
||||
"@graphql-codegen/cli": "^5.0.3",
|
||||
"@graphql-codegen/client-preset": "^4.5.0",
|
||||
"@graphql-codegen/plugin-helpers": "^5.1.0",
|
||||
"@graphql-codegen/typescript": "^4.1.1",
|
||||
"@graphql-codegen/visitor-plugin-common": "5.5.0",
|
||||
"@nuxt/devtools": "^1.3.9",
|
||||
"@nuxt/eslint": "^0.3.13",
|
||||
"@nuxt/image": "^1.8.1",
|
||||
|
|
|
@ -24,6 +24,7 @@ export default knexInstance
|
|||
export {
|
||||
knexInstance as db,
|
||||
knexInstance as knex,
|
||||
knexInstance as knexInstance,
|
||||
knexInstance as mainDb,
|
||||
knexInstance,
|
||||
config
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import { noop } from 'lodash'
|
||||
import { CommandModule } from 'yargs'
|
||||
|
||||
const command: CommandModule = {
|
||||
command: 'workspaces',
|
||||
describe: 'Various workspace related actions',
|
||||
builder(yargs) {
|
||||
return yargs.commandDir('workspaces', { extensions: ['js', 'ts'] }).demandCommand()
|
||||
},
|
||||
handler: noop
|
||||
}
|
||||
|
||||
export = command
|
|
@ -0,0 +1,67 @@
|
|||
import { CommandModule } from 'yargs'
|
||||
import { cliLogger } from '@/logging/logging'
|
||||
import { getWorkspaceBySlugOrIdFactory } from '@/modules/workspaces/repositories/workspaces'
|
||||
import { db } from '@/db/knex'
|
||||
import {
|
||||
PaidWorkspacePlanStatuses,
|
||||
PlanStatuses
|
||||
} from '@/modules/gatekeeper/domain/billing'
|
||||
import { upsertPaidWorkspacePlanFactory } from '@/modules/gatekeeper/repositories/billing'
|
||||
import { PaidWorkspacePlans } from '@/modules/gatekeeper/domain/workspacePricing'
|
||||
|
||||
const command: CommandModule<
|
||||
unknown,
|
||||
{
|
||||
workspaceSlugOrId: string
|
||||
status: PlanStatuses
|
||||
plan: PaidWorkspacePlans
|
||||
}
|
||||
> = {
|
||||
command: 'set-plan <workspaceSlugOrId> [plan] [status]',
|
||||
describe: 'Set a plan for a workspace.',
|
||||
builder: {
|
||||
workspaceSlugOrId: {
|
||||
describe: 'Workspace ID or slug',
|
||||
type: 'string'
|
||||
},
|
||||
plan: {
|
||||
describe: 'Plan to set the status for',
|
||||
type: 'string',
|
||||
default: 'business',
|
||||
choices: ['business', 'team', 'pro']
|
||||
},
|
||||
status: {
|
||||
describe: 'Status to set for the workspace plan',
|
||||
type: 'string',
|
||||
default: 'valid',
|
||||
choices: [
|
||||
'valid',
|
||||
'trial',
|
||||
'expired',
|
||||
'paymentFailed',
|
||||
'cancelationScheduled',
|
||||
'canceled'
|
||||
]
|
||||
}
|
||||
},
|
||||
handler: async (args) => {
|
||||
cliLogger.info(
|
||||
`Setting plan for workspace '${args.workspaceSlugOrId}' to '${args.plan}' with status '${args.status}'`
|
||||
)
|
||||
const workspace = await getWorkspaceBySlugOrIdFactory({ db })(args)
|
||||
if (!workspace) {
|
||||
throw new Error(`Workspace w/ slug or id '${args.workspaceSlugOrId}' not found`)
|
||||
}
|
||||
|
||||
await upsertPaidWorkspacePlanFactory({ db })({
|
||||
workspacePlan: {
|
||||
workspaceId: workspace.id,
|
||||
name: args.plan,
|
||||
status: args.status as PaidWorkspacePlanStatuses
|
||||
}
|
||||
})
|
||||
cliLogger.info(`Plan set!`)
|
||||
}
|
||||
}
|
||||
|
||||
export = command
|
|
@ -411,7 +411,7 @@ export const getPaginatedCommitCommentsTotalCountFactory =
|
|||
(deps: { db: Knex }): GetPaginatedCommitCommentsTotalCount =>
|
||||
async (params: Omit<PaginatedCommitCommentsParams, 'limit' | 'cursor'>) => {
|
||||
const baseQ = getPaginatedCommitCommentsBaseQueryFactory(deps)(params)
|
||||
const q = knex.count<{ count: string }[]>().from(baseQ.as('sq1'))
|
||||
const q = deps.db.count<{ count: string }[]>().from(baseQ.as('sq1'))
|
||||
const [row] = await q
|
||||
|
||||
return parseInt(row.count || '0')
|
||||
|
@ -476,7 +476,7 @@ export const getPaginatedBranchCommentsTotalCountFactory =
|
|||
(deps: { db: Knex }) =>
|
||||
async (params: Omit<PaginatedBranchCommentsParams, 'limit' | 'cursor'>) => {
|
||||
const baseQ = getPaginatedBranchCommentsBaseQueryFactory(deps)(params)
|
||||
const q = knex.count<{ count: string }[]>().from(baseQ.as('sq1'))
|
||||
const q = deps.db.count<{ count: string }[]>().from(baseQ.as('sq1'))
|
||||
const [row] = await q
|
||||
|
||||
return parseInt(row.count || '0')
|
||||
|
@ -673,7 +673,7 @@ export const getPaginatedProjectCommentsTotalCountFactory =
|
|||
params,
|
||||
options
|
||||
)
|
||||
const q = knex.count<{ count: string }[]>().from(baseQuery.as('sq1'))
|
||||
const q = deps.db.count<{ count: string }[]>().from(baseQuery.as('sq1'))
|
||||
const [row] = await q
|
||||
|
||||
return parseInt(row.count || '0')
|
||||
|
@ -866,7 +866,12 @@ export const getCommentsLegacyFactory =
|
|||
query.orderBy('createdAt', 'desc')
|
||||
query.limit(limit || 1) // need at least 1 row to get totalCount
|
||||
|
||||
const rows = await query
|
||||
const rows = (await query) as Array<
|
||||
CommentRecord & {
|
||||
total_count: string
|
||||
resources: Array<{ resourceId: string; resourceType: string }>
|
||||
}
|
||||
>
|
||||
const totalCount = rows && rows.length > 0 ? parseInt(rows[0].total_count) : 0
|
||||
const nextCursor = rows && rows.length > 0 ? rows[rows.length - 1].createdAt : null
|
||||
|
||||
|
|
|
@ -103,7 +103,6 @@ export = {
|
|||
},
|
||||
VersionMutations: {
|
||||
async moveToModel(_parent, args, ctx) {
|
||||
// TODO: how to get streamId here?
|
||||
const projectId = args.input.projectId
|
||||
const projectDb = await getProjectDbClient({ projectId })
|
||||
|
||||
|
@ -121,7 +120,6 @@ export = {
|
|||
return await batchMoveCommits(args.input, ctx.userId!)
|
||||
},
|
||||
async delete(_parent, args, ctx) {
|
||||
// TODO: how to get streamId here?
|
||||
const projectId = args.input.projectId
|
||||
const projectDb = await getProjectDbClient({ projectId })
|
||||
|
||||
|
@ -138,7 +136,6 @@ export = {
|
|||
return true
|
||||
},
|
||||
async update(_parent, args, ctx) {
|
||||
// TODO: how to get streamId here?
|
||||
const projectId = args.input.projectId
|
||||
const projectDb = await getProjectDbClient({ projectId })
|
||||
const stream = await ctx.loaders
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
CommitWithStreamBranchId,
|
||||
CommitWithStreamId,
|
||||
LegacyStreamCommit,
|
||||
LegacyUserCommit
|
||||
} from '@/modules/core/domain/commits/types'
|
||||
|
@ -12,7 +13,6 @@ import {
|
|||
import { Roles, ServerRoles, StreamRoles } from '@/modules/core/helpers/mainConstants'
|
||||
import {
|
||||
BranchRecord,
|
||||
CommitRecord,
|
||||
ObjectRecord,
|
||||
ServerInfo,
|
||||
StreamRecord,
|
||||
|
@ -36,7 +36,10 @@ export type StreamGraphQLReturn = StreamRecord & {
|
|||
role?: string | null
|
||||
}
|
||||
|
||||
export type CommitGraphQLReturn = CommitRecord | LegacyStreamCommit | LegacyUserCommit
|
||||
export type CommitGraphQLReturn =
|
||||
| CommitWithStreamId
|
||||
| LegacyStreamCommit
|
||||
| LegacyUserCommit
|
||||
|
||||
export type BranchGraphQLReturn = BranchRecord
|
||||
|
||||
|
|
|
@ -94,6 +94,10 @@ export async function buildRequestLoaders(
|
|||
* Get dataloaders for specific region
|
||||
*/
|
||||
const forRegion = (deps: { db: Knex }) => {
|
||||
if (deps.db === mainDb) {
|
||||
return mainDbLoaders
|
||||
}
|
||||
|
||||
if (!regionLoaders.has(deps.db)) {
|
||||
regionLoaders.set(deps.db, createLoadersForRegion(deps))
|
||||
}
|
||||
|
|
|
@ -404,7 +404,7 @@ export const getPaginatedProjectModelsTotalCountFactory =
|
|||
}
|
||||
|
||||
const baseQ = getPaginatedProjectModelsBaseQueryFactory(deps)(projectId, params)
|
||||
const q = knex.count<{ count: string }[]>().from(baseQ.as('sq1'))
|
||||
const q = deps.db.count<{ count: string }[]>().from(baseQ.as('sq1'))
|
||||
|
||||
const [res] = await q
|
||||
return parseInt(res?.count || '0')
|
||||
|
|
|
@ -343,7 +343,7 @@ describe('FileUploads @fileuploads', () => {
|
|||
.set('Authorization', `Bearer ${userOneToken}`)
|
||||
.set('Accept', 'application/json')
|
||||
.attach('test.ifc', require.resolve('@/readme.md'), 'test.ifc')
|
||||
expect(response.statusCode).to.equal(500) //FIXME should be 404 (technically a 401, but we don't want to leak existence of stream so 404 is preferrable)
|
||||
expect(response.statusCode).to.equal(404) //FIXME should be 404 (technically a 401, but we don't want to leak existence of stream so 404 is preferrable)
|
||||
const gqlResponse = await sendRequest(userOneToken, {
|
||||
query: `query ($streamId: String!) {
|
||||
stream(id: $streamId) {
|
||||
|
|
|
@ -19,6 +19,11 @@ export type PaidWorkspacePlanStatuses =
|
|||
|
||||
export type TrialWorkspacePlanStatuses = 'trial' | 'expired'
|
||||
|
||||
export type PlanStatuses =
|
||||
| PaidWorkspacePlanStatuses
|
||||
| TrialWorkspacePlanStatuses
|
||||
| UnpaidWorkspacePlanStatuses
|
||||
|
||||
type BaseWorkspacePlan = {
|
||||
workspaceId: string
|
||||
}
|
||||
|
|
|
@ -17,13 +17,15 @@ import {
|
|||
let multiRegionConfig: Optional<MultiRegionConfig> = undefined
|
||||
|
||||
const getMultiRegionConfig = async (): Promise<MultiRegionConfig> => {
|
||||
const emptyReturn = () => ({ main: { postgres: { connectionUri: '' } }, regions: {} })
|
||||
|
||||
if (isDevOrTestEnv() && !isMultiRegionEnabled()) {
|
||||
// this should throw somehow
|
||||
return { main: { postgres: { connectionUri: '' } }, regions: {} }
|
||||
return emptyReturn()
|
||||
}
|
||||
|
||||
if (!multiRegionConfig) {
|
||||
const relativePath = getMultiRegionConfigPath()
|
||||
const relativePath = getMultiRegionConfigPath({ unsafe: isDevOrTestEnv() })
|
||||
if (!relativePath) return emptyReturn()
|
||||
|
||||
const configPath = path.resolve(packageRoot, relativePath)
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { DataRegionsConfig } from '@/modules/multiregion/domain/types'
|
||||
import { Regions } from '@/modules/multiregion/repositories'
|
||||
import { isMultiRegionEnabled } from '@/modules/multiregion/helpers'
|
||||
import { BasicTestUser, createTestUser } from '@/test/authHelper'
|
||||
import {
|
||||
CreateNewRegionDocument,
|
||||
|
@ -14,260 +14,269 @@ import {
|
|||
testApolloServer,
|
||||
TestApolloServer
|
||||
} from '@/test/graphqlHelper'
|
||||
import { beforeEachContext, truncateTables } from '@/test/hooks'
|
||||
import { beforeEachContext, getRegionKeys } from '@/test/hooks'
|
||||
import { MultiRegionConfigMock, MultiRegionDbSelectorMock } from '@/test/mocks/global'
|
||||
import { truncateRegionsSafely } from '@/test/speckle-helpers/regions'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { expect } from 'chai'
|
||||
|
||||
describe('Multi Region Server Settings', () => {
|
||||
let testAdminUser: BasicTestUser
|
||||
let testBasicUser: BasicTestUser
|
||||
let apollo: TestApolloServer
|
||||
const isEnabled = isMultiRegionEnabled()
|
||||
|
||||
const fakeRegionKey1 = 'us-west-1'
|
||||
const fakeRegionKey2 = 'eu-east-2'
|
||||
isEnabled
|
||||
? describe('Multi Region Server Settings', () => {
|
||||
let testAdminUser: BasicTestUser
|
||||
let testBasicUser: BasicTestUser
|
||||
let apollo: TestApolloServer
|
||||
|
||||
const fakeRegionConfig: DataRegionsConfig = {
|
||||
[fakeRegionKey1]: {
|
||||
postgres: {
|
||||
connectionUri: 'postgres://user:password@uswest1:port/dbname'
|
||||
}
|
||||
},
|
||||
[fakeRegionKey2]: {
|
||||
postgres: {
|
||||
connectionUri: 'postgres://user:password@eueast3:port/dbname'
|
||||
}
|
||||
}
|
||||
}
|
||||
const fakeRegionKey1 = 'us-west-1'
|
||||
const fakeRegionKey2 = 'eu-east-2'
|
||||
|
||||
before(async () => {
|
||||
MultiRegionConfigMock.mockFunction(
|
||||
'getAvailableRegionConfig',
|
||||
async () => fakeRegionConfig
|
||||
)
|
||||
MultiRegionDbSelectorMock.mockFunction('initializeRegion', async () =>
|
||||
Promise.resolve()
|
||||
)
|
||||
|
||||
await beforeEachContext()
|
||||
testAdminUser = await createTestUser({ role: Roles.Server.Admin })
|
||||
testBasicUser = await createTestUser({ role: Roles.Server.User })
|
||||
apollo = await testApolloServer({ authUserId: testAdminUser.id })
|
||||
})
|
||||
|
||||
after(() => {
|
||||
MultiRegionConfigMock.resetMockedFunctions()
|
||||
MultiRegionDbSelectorMock.resetMockedFunctions()
|
||||
})
|
||||
|
||||
describe('server config', () => {
|
||||
const createRegion = (
|
||||
input: CreateServerRegionInput,
|
||||
options?: ExecuteOperationOptions
|
||||
) => apollo.execute(CreateNewRegionDocument, { input }, options)
|
||||
|
||||
it("region keys can't be retrieved by non-admin", async () => {
|
||||
const res = await apollo.execute(
|
||||
GetAvailableRegionKeysDocument,
|
||||
{},
|
||||
{
|
||||
context: {
|
||||
userId: testBasicUser.id,
|
||||
role: Roles.Server.User
|
||||
const fakeRegionConfig: DataRegionsConfig = {
|
||||
[fakeRegionKey1]: {
|
||||
postgres: {
|
||||
connectionUri: 'postgres://user:password@uswest1:port/dbname'
|
||||
}
|
||||
},
|
||||
[fakeRegionKey2]: {
|
||||
postgres: {
|
||||
connectionUri: 'postgres://user:password@eueast3:port/dbname'
|
||||
}
|
||||
}
|
||||
)
|
||||
expect(res).to.haveGraphQLErrors('You do not have the required server role')
|
||||
expect(res.data?.serverInfo.multiRegion.availableKeys).to.be.not.ok
|
||||
})
|
||||
|
||||
it('allows retrieving available config keys', async () => {
|
||||
const res = await apollo.execute(GetAvailableRegionKeysDocument, {})
|
||||
expect(res.data?.serverInfo.multiRegion.availableKeys).to.deep.equal(
|
||||
Object.keys(fakeRegionConfig)
|
||||
)
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
})
|
||||
|
||||
describe('when creating new region', async () => {
|
||||
afterEach(async () => {
|
||||
// Wipe created regions
|
||||
await truncateTables([Regions.name])
|
||||
})
|
||||
|
||||
it("it can't be created by non-admin", async () => {
|
||||
const res = await createRegion(
|
||||
{
|
||||
key: fakeRegionKey1,
|
||||
name: 'US West 1',
|
||||
description: 'Helloooo'
|
||||
},
|
||||
{
|
||||
context: {
|
||||
userId: testBasicUser.id,
|
||||
role: Roles.Server.User
|
||||
}
|
||||
}
|
||||
)
|
||||
expect(res).to.haveGraphQLErrors('You do not have the required server role')
|
||||
})
|
||||
|
||||
it('it works with valid input', async () => {
|
||||
const input: CreateServerRegionInput = {
|
||||
key: fakeRegionKey1,
|
||||
name: 'US West 1',
|
||||
description: 'Helloooo'
|
||||
}
|
||||
|
||||
const res = await createRegion(input)
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
expect(res.data?.serverInfoMutations.multiRegion.create).to.deep.equal({
|
||||
...input,
|
||||
id: input.key
|
||||
})
|
||||
})
|
||||
|
||||
it("doesn't work with already used up key", async () => {
|
||||
const input: CreateServerRegionInput = {
|
||||
key: fakeRegionKey1,
|
||||
name: 'US West 1',
|
||||
description: 'Helloooo'
|
||||
}
|
||||
|
||||
const res1 = await createRegion(input)
|
||||
expect(res1).to.not.haveGraphQLErrors()
|
||||
|
||||
const res2 = await createRegion(input)
|
||||
expect(res2).to.haveGraphQLErrors('Region with this key already exists')
|
||||
})
|
||||
|
||||
it("doesn't work with invalid key", async () => {
|
||||
const input: CreateServerRegionInput = {
|
||||
key: 'randooo-key',
|
||||
name: 'US West 1',
|
||||
description: 'Helloooo'
|
||||
}
|
||||
|
||||
const res = await createRegion(input)
|
||||
expect(res).to.haveGraphQLErrors('Region key is not valid')
|
||||
})
|
||||
})
|
||||
|
||||
describe('when working with existing regions', async () => {
|
||||
const createdRegionInput: CreateServerRegionInput = {
|
||||
key: fakeRegionKey1,
|
||||
name: 'US West 1',
|
||||
description: 'Helloooo'
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
// Create a region
|
||||
await createRegion(createdRegionInput, { assertNoErrors: true })
|
||||
})
|
||||
|
||||
it("can't retrieve regions if non-admin", async () => {
|
||||
const res = await apollo.execute(
|
||||
GetRegionsDocument,
|
||||
{},
|
||||
{
|
||||
context: {
|
||||
userId: testBasicUser.id,
|
||||
role: Roles.Server.User
|
||||
}
|
||||
}
|
||||
MultiRegionConfigMock.mockFunction(
|
||||
'getAvailableRegionConfig',
|
||||
async () => fakeRegionConfig
|
||||
)
|
||||
expect(res).to.haveGraphQLErrors('You do not have the required server role')
|
||||
MultiRegionDbSelectorMock.mockFunction('initializeRegion', async () =>
|
||||
Promise.resolve()
|
||||
)
|
||||
|
||||
await beforeEachContext()
|
||||
testAdminUser = await createTestUser({ role: Roles.Server.Admin })
|
||||
testBasicUser = await createTestUser({ role: Roles.Server.User })
|
||||
apollo = await testApolloServer({ authUserId: testAdminUser.id })
|
||||
})
|
||||
|
||||
it('allows retrieving all regions', async () => {
|
||||
const res = await apollo.execute(GetRegionsDocument, {})
|
||||
after(() => {
|
||||
MultiRegionConfigMock.resetMockedFunctions()
|
||||
MultiRegionDbSelectorMock.resetMockedFunctions()
|
||||
})
|
||||
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
expect(res.data?.serverInfo.multiRegion.regions).to.have.length(1)
|
||||
expect(res.data?.serverInfo.multiRegion.regions).to.deep.equal([
|
||||
{
|
||||
...createdRegionInput,
|
||||
id: createdRegionInput.key
|
||||
describe('server config', () => {
|
||||
const createRegion = (
|
||||
input: CreateServerRegionInput,
|
||||
options?: ExecuteOperationOptions
|
||||
) => apollo.execute(CreateNewRegionDocument, { input }, options)
|
||||
|
||||
it("region keys can't be retrieved by non-admin", async () => {
|
||||
const res = await apollo.execute(
|
||||
GetAvailableRegionKeysDocument,
|
||||
{},
|
||||
{
|
||||
context: {
|
||||
userId: testBasicUser.id,
|
||||
role: Roles.Server.User
|
||||
}
|
||||
}
|
||||
)
|
||||
expect(res).to.haveGraphQLErrors('You do not have the required server role')
|
||||
expect(res.data?.serverInfo.multiRegion.availableKeys).to.be.not.ok
|
||||
})
|
||||
|
||||
it('allows retrieving available config keys', async () => {
|
||||
const res = await apollo.execute(GetAvailableRegionKeysDocument, {})
|
||||
expect(res.data?.serverInfo.multiRegion.availableKeys).to.deep.equal(
|
||||
Object.keys(fakeRegionConfig)
|
||||
)
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
})
|
||||
|
||||
describe('when creating new region', async () => {
|
||||
afterEach(async () => {
|
||||
// Wipe created regions
|
||||
await truncateRegionsSafely()
|
||||
})
|
||||
|
||||
it("it can't be created by non-admin", async () => {
|
||||
const res = await createRegion(
|
||||
{
|
||||
key: fakeRegionKey1,
|
||||
name: 'US West 1',
|
||||
description: 'Helloooo'
|
||||
},
|
||||
{
|
||||
context: {
|
||||
userId: testBasicUser.id,
|
||||
role: Roles.Server.User
|
||||
}
|
||||
}
|
||||
)
|
||||
expect(res).to.haveGraphQLErrors('You do not have the required server role')
|
||||
})
|
||||
|
||||
it('it works with valid input', async () => {
|
||||
const input: CreateServerRegionInput = {
|
||||
key: fakeRegionKey1,
|
||||
name: 'US West 1',
|
||||
description: 'Helloooo'
|
||||
}
|
||||
|
||||
const res = await createRegion(input)
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
expect(res.data?.serverInfoMutations.multiRegion.create).to.deep.equal({
|
||||
...input,
|
||||
id: input.key
|
||||
})
|
||||
})
|
||||
|
||||
it("doesn't work with already used up key", async () => {
|
||||
const input: CreateServerRegionInput = {
|
||||
key: fakeRegionKey1,
|
||||
name: 'US West 1',
|
||||
description: 'Helloooo'
|
||||
}
|
||||
|
||||
const res1 = await createRegion(input)
|
||||
expect(res1).to.not.haveGraphQLErrors()
|
||||
|
||||
const res2 = await createRegion(input)
|
||||
expect(res2).to.haveGraphQLErrors('Region with this key already exists')
|
||||
})
|
||||
|
||||
it("doesn't work with invalid key", async () => {
|
||||
const input: CreateServerRegionInput = {
|
||||
key: 'randooo-key',
|
||||
name: 'US West 1',
|
||||
description: 'Helloooo'
|
||||
}
|
||||
|
||||
const res = await createRegion(input)
|
||||
expect(res).to.haveGraphQLErrors('Region key is not valid')
|
||||
})
|
||||
})
|
||||
|
||||
describe('when working with existing regions', async () => {
|
||||
const createdRegionInput: CreateServerRegionInput = {
|
||||
key: fakeRegionKey1,
|
||||
name: 'US West 1',
|
||||
description: 'Helloooo'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('filters out used region from available keys', async () => {
|
||||
const res = await apollo.execute(GetAvailableRegionKeysDocument, {})
|
||||
before(async () => {
|
||||
// Create a region
|
||||
await createRegion(createdRegionInput, { assertNoErrors: true })
|
||||
})
|
||||
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
expect(res.data?.serverInfo.multiRegion.availableKeys).to.deep.equal([
|
||||
fakeRegionKey2
|
||||
])
|
||||
it("can't retrieve regions if non-admin", async () => {
|
||||
const res = await apollo.execute(
|
||||
GetRegionsDocument,
|
||||
{},
|
||||
{
|
||||
context: {
|
||||
userId: testBasicUser.id,
|
||||
role: Roles.Server.User
|
||||
}
|
||||
}
|
||||
)
|
||||
expect(res).to.haveGraphQLErrors('You do not have the required server role')
|
||||
})
|
||||
|
||||
it('allows retrieving all regions', async () => {
|
||||
const res = await apollo.execute(GetRegionsDocument, {})
|
||||
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
expect(res.data?.serverInfo.multiRegion.regions).to.have.length(
|
||||
1 + getRegionKeys().length
|
||||
)
|
||||
expect(
|
||||
res.data?.serverInfo.multiRegion.regions.find(
|
||||
(r) => r.id === createdRegionInput.key
|
||||
)
|
||||
).to.deep.equal({
|
||||
...createdRegionInput,
|
||||
id: createdRegionInput.key
|
||||
})
|
||||
})
|
||||
|
||||
it('filters out used region from available keys', async () => {
|
||||
const res = await apollo.execute(GetAvailableRegionKeysDocument, {})
|
||||
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
expect(res.data?.serverInfo.multiRegion.availableKeys).to.deep.equal([
|
||||
fakeRegionKey2
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('when updating existing region', async () => {
|
||||
const createdRegionInput: CreateServerRegionInput = {
|
||||
key: fakeRegionKey2,
|
||||
name: 'Updatable Region 1',
|
||||
description: 'Helloooo'
|
||||
}
|
||||
|
||||
const updateRegion = (
|
||||
input: UpdateServerRegionInput,
|
||||
options?: ExecuteOperationOptions
|
||||
) => apollo.execute(UpdateRegionDocument, { input }, options)
|
||||
|
||||
beforeEach(async () => {
|
||||
// Create new region
|
||||
await createRegion(createdRegionInput, { assertNoErrors: true })
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
// Wipe created regions
|
||||
await truncateRegionsSafely()
|
||||
})
|
||||
|
||||
it("can't update region if non-admin", async () => {
|
||||
const res = await updateRegion(
|
||||
{
|
||||
key: createdRegionInput.key,
|
||||
name: 'Updated Region 1'
|
||||
},
|
||||
{
|
||||
context: {
|
||||
userId: testBasicUser.id,
|
||||
role: Roles.Server.User
|
||||
}
|
||||
}
|
||||
)
|
||||
expect(res).to.haveGraphQLErrors('You do not have the required server role')
|
||||
})
|
||||
|
||||
it('works with valid input', async () => {
|
||||
const updatedName = 'aaa Updated Region 1'
|
||||
const updatedDescription = 'bbb Updated description'
|
||||
|
||||
const res = await updateRegion({
|
||||
key: createdRegionInput.key,
|
||||
name: updatedName,
|
||||
description: updatedDescription
|
||||
})
|
||||
|
||||
expect(res.data?.serverInfoMutations.multiRegion.update).to.deep.equal({
|
||||
...createdRegionInput,
|
||||
id: createdRegionInput.key,
|
||||
name: updatedName,
|
||||
description: updatedDescription
|
||||
})
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
})
|
||||
|
||||
it('fails gracefully with invalid region key', async () => {
|
||||
const res = await updateRegion({
|
||||
key: 'invalid-key',
|
||||
name: 'Updated Region 1'
|
||||
})
|
||||
|
||||
expect(res).to.haveGraphQLErrors('Region not found')
|
||||
expect(res.data?.serverInfoMutations.multiRegion.update).to.be.not.ok
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when updating existing region', async () => {
|
||||
const createdRegionInput: CreateServerRegionInput = {
|
||||
key: fakeRegionKey2,
|
||||
name: 'Updatable Region 1',
|
||||
description: 'Helloooo'
|
||||
}
|
||||
|
||||
const updateRegion = (
|
||||
input: UpdateServerRegionInput,
|
||||
options?: ExecuteOperationOptions
|
||||
) => apollo.execute(UpdateRegionDocument, { input }, options)
|
||||
|
||||
beforeEach(async () => {
|
||||
// Create new region
|
||||
await createRegion(createdRegionInput, { assertNoErrors: true })
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
// Wipe created regions
|
||||
await truncateTables([Regions.name])
|
||||
})
|
||||
|
||||
it("can't update region if non-admin", async () => {
|
||||
const res = await updateRegion(
|
||||
{
|
||||
key: createdRegionInput.key,
|
||||
name: 'Updated Region 1'
|
||||
},
|
||||
{
|
||||
context: {
|
||||
userId: testBasicUser.id,
|
||||
role: Roles.Server.User
|
||||
}
|
||||
}
|
||||
)
|
||||
expect(res).to.haveGraphQLErrors('You do not have the required server role')
|
||||
})
|
||||
|
||||
it('works with valid input', async () => {
|
||||
const updatedName = 'aaa Updated Region 1'
|
||||
const updatedDescription = 'bbb Updated description'
|
||||
|
||||
const res = await updateRegion({
|
||||
key: createdRegionInput.key,
|
||||
name: updatedName,
|
||||
description: updatedDescription
|
||||
})
|
||||
|
||||
expect(res.data?.serverInfoMutations.multiRegion.update).to.deep.equal({
|
||||
...createdRegionInput,
|
||||
id: createdRegionInput.key,
|
||||
name: updatedName,
|
||||
description: updatedDescription
|
||||
})
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
})
|
||||
|
||||
it('fails gracefully with invalid region key', async () => {
|
||||
const res = await updateRegion({
|
||||
key: 'invalid-key',
|
||||
name: 'Updated Region 1'
|
||||
})
|
||||
|
||||
expect(res).to.haveGraphQLErrors('Region not found')
|
||||
expect(res.data?.serverInfoMutations.multiRegion.update).to.be.not.ok
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
: void 0
|
||||
|
|
|
@ -6,8 +6,8 @@ import {
|
|||
ForbiddenError,
|
||||
UnauthorizedError,
|
||||
ContextError,
|
||||
BadRequestError,
|
||||
DatabaseError
|
||||
DatabaseError,
|
||||
NotFoundError
|
||||
} from '@/modules/shared/errors'
|
||||
import { adminOverrideEnabled } from '@/modules/shared/helpers/envHelper'
|
||||
import {
|
||||
|
@ -247,7 +247,7 @@ export const validateRequiredStreamFactory =
|
|||
if (!stream)
|
||||
return authFailed(
|
||||
context,
|
||||
new BadRequestError('Stream inputs are malformed'),
|
||||
new NotFoundError('Stream inputs are malformed'),
|
||||
true
|
||||
)
|
||||
context.stream = stream
|
||||
|
|
|
@ -53,9 +53,18 @@ export function getBooleanFromEnv(envVarKey: string, aDefault = false): boolean
|
|||
return ['1', 'true', true].includes(process.env[envVarKey] || aDefault.toString())
|
||||
}
|
||||
|
||||
export function getStringFromEnv(envVarKey: string): string {
|
||||
export function getStringFromEnv(
|
||||
envVarKey: string,
|
||||
options?: Partial<{
|
||||
/**
|
||||
* If set to true, wont throw if the env var is not set
|
||||
*/
|
||||
unsafe: boolean
|
||||
}>
|
||||
): string {
|
||||
const envVar = process.env[envVarKey]
|
||||
if (!envVar) {
|
||||
if (options?.unsafe) return ''
|
||||
throw new MisconfiguredEnvironmentError(`${envVarKey} env var not configured`)
|
||||
}
|
||||
return envVar
|
||||
|
@ -415,8 +424,8 @@ export function getOtelHeaderValue() {
|
|||
return getStringFromEnv('OTEL_TRACE_VALUE')
|
||||
}
|
||||
|
||||
export function getMultiRegionConfigPath() {
|
||||
return getStringFromEnv('MULTI_REGION_CONFIG_PATH')
|
||||
export function getMultiRegionConfigPath(options?: Partial<{ unsafe: boolean }>) {
|
||||
return getStringFromEnv('MULTI_REGION_CONFIG_PATH', options)
|
||||
}
|
||||
|
||||
export const shouldRunTestsInMultiregionMode = () =>
|
||||
|
|
|
@ -6,7 +6,11 @@ import {
|
|||
authHasFailed
|
||||
} from '@/modules/shared/authz'
|
||||
import { Request, Response, NextFunction, Handler } from 'express'
|
||||
import { ForbiddenError, UnauthorizedError } from '@/modules/shared/errors'
|
||||
import {
|
||||
ForbiddenError,
|
||||
NotFoundError,
|
||||
UnauthorizedError
|
||||
} from '@/modules/shared/errors'
|
||||
import { ensureError } from '@/modules/shared/helpers/errorHelper'
|
||||
import { TokenValidationResult } from '@/modules/core/helpers/types'
|
||||
import { buildRequestLoaders } from '@/modules/core/loaders'
|
||||
|
@ -54,6 +58,7 @@ export const authMiddlewareCreator = (steps: AuthPipelineFunction[]) => {
|
|||
message = authResult.error?.message || message
|
||||
if (authResult.error instanceof UnauthorizedError) status = 401
|
||||
if (authResult.error instanceof ForbiddenError) status = 403
|
||||
if (authResult.error instanceof NotFoundError) status = 404
|
||||
}
|
||||
return res.status(status).json({ error: message })
|
||||
}
|
||||
|
|
|
@ -14,9 +14,9 @@ const {
|
|||
const {
|
||||
ForbiddenError: SFE,
|
||||
UnauthorizedError: SUE,
|
||||
BadRequestError,
|
||||
UnauthorizedError,
|
||||
ContextError
|
||||
ContextError,
|
||||
NotFoundError
|
||||
} = require('@/modules/shared/errors')
|
||||
const { Roles } = require('@speckle/shared')
|
||||
const {
|
||||
|
@ -380,7 +380,7 @@ describe('AuthZ @shared', () => {
|
|||
context: {}
|
||||
})
|
||||
|
||||
expectAuthError(new BadRequestError('Stream inputs are malformed'), authResult)
|
||||
expectAuthError(new NotFoundError('Stream inputs are malformed'), authResult)
|
||||
})
|
||||
})
|
||||
describe('Escape hatches', () => {
|
||||
|
|
|
@ -63,6 +63,11 @@ export type GetWorkspaceBySlug = (args: {
|
|||
userId?: string
|
||||
}) => Promise<WorkspaceWithOptionalRole | null>
|
||||
|
||||
// Useful for dev purposes (e.g. CLI)
|
||||
export type GetWorkspaceBySlugOrId = (args: {
|
||||
workspaceSlugOrId: string
|
||||
}) => Promise<Workspace | null>
|
||||
|
||||
export type GetWorkspaces = (args: {
|
||||
workspaceIds: string[]
|
||||
userId?: string
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
GetUserIdsWithRoleInWorkspace,
|
||||
GetWorkspace,
|
||||
GetWorkspaceBySlug,
|
||||
GetWorkspaceBySlugOrId,
|
||||
GetWorkspaceCollaborators,
|
||||
GetWorkspaceCollaboratorsTotalCount,
|
||||
GetWorkspaceDomains,
|
||||
|
@ -146,6 +147,18 @@ export const getWorkspaceFactory =
|
|||
return workspace || null
|
||||
}
|
||||
|
||||
export const getWorkspaceBySlugOrIdFactory =
|
||||
(deps: { db: Knex }): GetWorkspaceBySlugOrId =>
|
||||
async ({ workspaceSlugOrId }) => {
|
||||
const { db } = deps
|
||||
const workspace = await workspaceWithRoleBaseQuery({ db })
|
||||
.where(Workspaces.col.slug, workspaceSlugOrId)
|
||||
.orWhere(Workspaces.col.id, workspaceSlugOrId)
|
||||
.first()
|
||||
|
||||
return workspace || null
|
||||
}
|
||||
|
||||
export const getWorkspaceBySlugFactory =
|
||||
({ db }: { db: Knex }): GetWorkspaceBySlug =>
|
||||
async ({ workspaceSlug, userId }) => {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { db } from '@/db/knex'
|
||||
import { isMultiRegionEnabled } from '@/modules/multiregion/helpers'
|
||||
import { storeRegionFactory } from '@/modules/multiregion/repositories'
|
||||
import { WorkspaceRegions } from '@/modules/workspaces/repositories/regions'
|
||||
import {
|
||||
|
@ -20,180 +21,184 @@ import { Roles } from '@speckle/shared'
|
|||
import { expect } from 'chai'
|
||||
|
||||
const storeRegion = storeRegionFactory({ db })
|
||||
const isEnabled = isMultiRegionEnabled()
|
||||
|
||||
describe('Workspace regions GQL', () => {
|
||||
const region1Key = 'us-west-1'
|
||||
const region2Key = 'eu-west-2'
|
||||
isEnabled
|
||||
? describe('Workspace regions GQL', () => {
|
||||
const region1Key = 'us-west-1'
|
||||
const region2Key = 'eu-west-2'
|
||||
|
||||
let me: BasicTestUser
|
||||
let otherGuy: BasicTestUser
|
||||
let me: BasicTestUser
|
||||
let otherGuy: BasicTestUser
|
||||
|
||||
const myFirstWorkspace: BasicTestWorkspace = {
|
||||
id: '',
|
||||
ownerId: '',
|
||||
slug: '',
|
||||
name: 'My first workspace'
|
||||
}
|
||||
const myFirstWorkspace: BasicTestWorkspace = {
|
||||
id: '',
|
||||
ownerId: '',
|
||||
slug: '',
|
||||
name: 'My first workspace'
|
||||
}
|
||||
|
||||
let apollo: TestApolloServer
|
||||
let apollo: TestApolloServer
|
||||
|
||||
before(async () => {
|
||||
MultiRegionDbSelectorMock.mockFunction('getDb', async () => db)
|
||||
MultiRegionDbSelectorMock.mockFunction('getRegionDb', async () => db)
|
||||
before(async () => {
|
||||
MultiRegionDbSelectorMock.mockFunction('getDb', async () => db)
|
||||
MultiRegionDbSelectorMock.mockFunction('getRegionDb', async () => db)
|
||||
|
||||
await beforeEachContext()
|
||||
await beforeEachContext()
|
||||
|
||||
me = await createTestUser({ role: Roles.Server.Admin })
|
||||
otherGuy = await createTestUser({ role: Roles.Server.User })
|
||||
me = await createTestUser({ role: Roles.Server.Admin })
|
||||
otherGuy = await createTestUser({ role: Roles.Server.User })
|
||||
|
||||
await Promise.all([
|
||||
// Create first test workspace
|
||||
createTestWorkspace(myFirstWorkspace, me),
|
||||
// Create a couple of test regions
|
||||
storeRegion({
|
||||
region: {
|
||||
key: region1Key,
|
||||
name: 'US West 1'
|
||||
}
|
||||
}),
|
||||
storeRegion({
|
||||
region: {
|
||||
key: region2Key,
|
||||
name: 'EU West 2'
|
||||
}
|
||||
})
|
||||
])
|
||||
await Promise.all([
|
||||
// Create first test workspace
|
||||
createTestWorkspace(myFirstWorkspace, me),
|
||||
// Create a couple of test regions
|
||||
storeRegion({
|
||||
region: {
|
||||
key: region1Key,
|
||||
name: 'US West 1'
|
||||
}
|
||||
}),
|
||||
storeRegion({
|
||||
region: {
|
||||
key: region2Key,
|
||||
name: 'EU West 2'
|
||||
}
|
||||
})
|
||||
])
|
||||
|
||||
await Promise.all([
|
||||
// Make otherGuy member of my workspace
|
||||
assignToWorkspace(myFirstWorkspace, otherGuy)
|
||||
])
|
||||
await Promise.all([
|
||||
// Make otherGuy member of my workspace
|
||||
assignToWorkspace(myFirstWorkspace, otherGuy)
|
||||
])
|
||||
|
||||
apollo = await testApolloServer({ authUserId: me.id })
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
MultiRegionDbSelectorMock.resetMockedFunctions()
|
||||
await truncateRegionsSafely()
|
||||
})
|
||||
|
||||
describe('when listing', () => {
|
||||
it("can't list if not workspace admin", async () => {
|
||||
const res = await apollo.execute(
|
||||
GetWorkspaceAvailableRegionsDocument,
|
||||
{ workspaceId: myFirstWorkspace.id },
|
||||
{ authUserId: otherGuy.id }
|
||||
)
|
||||
|
||||
expect(res.data?.workspace.availableRegions).to.be.not.ok
|
||||
expect(res).to.haveGraphQLErrors('You are not authorized')
|
||||
})
|
||||
|
||||
it('can list if workspace admin', async () => {
|
||||
const res = await apollo.execute(GetWorkspaceAvailableRegionsDocument, {
|
||||
workspaceId: myFirstWorkspace.id
|
||||
apollo = await testApolloServer({ authUserId: me.id })
|
||||
})
|
||||
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
expect(
|
||||
res.data?.workspace.availableRegions.map((r) => r.key)
|
||||
).to.deep.equalInAnyOrder([region1Key, region2Key, ...getRegionKeys()])
|
||||
})
|
||||
})
|
||||
after(async () => {
|
||||
MultiRegionDbSelectorMock.resetMockedFunctions()
|
||||
await truncateRegionsSafely()
|
||||
})
|
||||
|
||||
describe('when setting default region', () => {
|
||||
const mySecondWorkspace: BasicTestWorkspace = {
|
||||
id: '',
|
||||
ownerId: '',
|
||||
slug: '',
|
||||
name: 'My second workspace'
|
||||
}
|
||||
describe('when listing', () => {
|
||||
it("can't list if not workspace admin", async () => {
|
||||
const res = await apollo.execute(
|
||||
GetWorkspaceAvailableRegionsDocument,
|
||||
{ workspaceId: myFirstWorkspace.id },
|
||||
{ authUserId: otherGuy.id }
|
||||
)
|
||||
|
||||
before(async () => {
|
||||
await createTestWorkspace(mySecondWorkspace, me)
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await db
|
||||
.from(WorkspaceRegions.name)
|
||||
.where({
|
||||
[WorkspaceRegions.col.workspaceId]: mySecondWorkspace.id
|
||||
expect(res.data?.workspace.availableRegions).to.be.not.ok
|
||||
expect(res).to.haveGraphQLErrors('You are not authorized')
|
||||
})
|
||||
.delete()
|
||||
})
|
||||
|
||||
it("can't set default region if not workspace admin", async () => {
|
||||
const res = await apollo.execute(
|
||||
SetWorkspaceDefaultRegionDocument,
|
||||
{ workspaceId: mySecondWorkspace.id, regionKey: region1Key },
|
||||
{ authUserId: otherGuy.id }
|
||||
)
|
||||
it('can list if workspace admin', async () => {
|
||||
const res = await apollo.execute(GetWorkspaceAvailableRegionsDocument, {
|
||||
workspaceId: myFirstWorkspace.id
|
||||
})
|
||||
|
||||
expect(res).to.haveGraphQLErrors('You are not authorized')
|
||||
expect(res.data?.workspaceMutations.setDefaultRegion).to.be.not.ok
|
||||
})
|
||||
|
||||
it('can set default region if workspace admin', async () => {
|
||||
const res = await apollo.execute(SetWorkspaceDefaultRegionDocument, {
|
||||
workspaceId: mySecondWorkspace.id,
|
||||
regionKey: region1Key
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
expect(
|
||||
res.data?.workspace.availableRegions.map((r) => r.key)
|
||||
).to.deep.equalInAnyOrder([region1Key, region2Key, ...getRegionKeys()])
|
||||
})
|
||||
})
|
||||
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
expect(res.data?.workspaceMutations.setDefaultRegion.defaultRegion?.key).to.equal(
|
||||
region1Key
|
||||
)
|
||||
})
|
||||
})
|
||||
describe('when setting default region', () => {
|
||||
const mySecondWorkspace: BasicTestWorkspace = {
|
||||
id: '',
|
||||
ownerId: '',
|
||||
slug: '',
|
||||
name: 'My second workspace'
|
||||
}
|
||||
|
||||
describe('with existing default region', () => {
|
||||
const myThirdWorkspace: BasicTestWorkspace = {
|
||||
id: '',
|
||||
ownerId: '',
|
||||
slug: '',
|
||||
name: 'My third workspace'
|
||||
}
|
||||
before(async () => {
|
||||
await createTestWorkspace(mySecondWorkspace, me)
|
||||
})
|
||||
|
||||
before(async () => {
|
||||
await createTestWorkspace(myThirdWorkspace, me)
|
||||
await apollo.execute(
|
||||
SetWorkspaceDefaultRegionDocument,
|
||||
{
|
||||
workspaceId: myThirdWorkspace.id,
|
||||
regionKey: region1Key
|
||||
},
|
||||
{ assertNoErrors: true }
|
||||
)
|
||||
})
|
||||
beforeEach(async () => {
|
||||
await db
|
||||
.from(WorkspaceRegions.name)
|
||||
.where({
|
||||
[WorkspaceRegions.col.workspaceId]: mySecondWorkspace.id
|
||||
})
|
||||
.delete()
|
||||
})
|
||||
|
||||
it("can't override default region", async () => {
|
||||
const res = await apollo.execute(SetWorkspaceDefaultRegionDocument, {
|
||||
workspaceId: myThirdWorkspace.id,
|
||||
regionKey: region2Key
|
||||
it("can't set default region if not workspace admin", async () => {
|
||||
const res = await apollo.execute(
|
||||
SetWorkspaceDefaultRegionDocument,
|
||||
{ workspaceId: mySecondWorkspace.id, regionKey: region1Key },
|
||||
{ authUserId: otherGuy.id }
|
||||
)
|
||||
|
||||
expect(res).to.haveGraphQLErrors('You are not authorized')
|
||||
expect(res.data?.workspaceMutations.setDefaultRegion).to.be.not.ok
|
||||
})
|
||||
|
||||
it('can set default region if workspace admin', async () => {
|
||||
const res = await apollo.execute(SetWorkspaceDefaultRegionDocument, {
|
||||
workspaceId: mySecondWorkspace.id,
|
||||
regionKey: region1Key
|
||||
})
|
||||
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
expect(
|
||||
res.data?.workspaceMutations.setDefaultRegion.defaultRegion?.key
|
||||
).to.equal(region1Key)
|
||||
})
|
||||
})
|
||||
|
||||
expect(res).to.haveGraphQLErrors('Workspace already has a region assigned')
|
||||
expect(res.data?.workspaceMutations.setDefaultRegion.defaultRegion).to.be.not.ok
|
||||
})
|
||||
describe('with existing default region', () => {
|
||||
const myThirdWorkspace: BasicTestWorkspace = {
|
||||
id: '',
|
||||
ownerId: '',
|
||||
slug: '',
|
||||
name: 'My third workspace'
|
||||
}
|
||||
|
||||
it('can list default region if workspace admin', async () => {
|
||||
const res = await apollo.execute(GetWorkspaceDefaultRegionDocument, {
|
||||
workspaceId: myThirdWorkspace.id
|
||||
before(async () => {
|
||||
await createTestWorkspace(myThirdWorkspace, me)
|
||||
await apollo.execute(
|
||||
SetWorkspaceDefaultRegionDocument,
|
||||
{
|
||||
workspaceId: myThirdWorkspace.id,
|
||||
regionKey: region1Key
|
||||
},
|
||||
{ assertNoErrors: true }
|
||||
)
|
||||
})
|
||||
|
||||
it("can't override default region", async () => {
|
||||
const res = await apollo.execute(SetWorkspaceDefaultRegionDocument, {
|
||||
workspaceId: myThirdWorkspace.id,
|
||||
regionKey: region2Key
|
||||
})
|
||||
|
||||
expect(res).to.haveGraphQLErrors('Workspace already has a region assigned')
|
||||
expect(res.data?.workspaceMutations.setDefaultRegion.defaultRegion).to.be.not
|
||||
.ok
|
||||
})
|
||||
|
||||
it('can list default region if workspace admin', async () => {
|
||||
const res = await apollo.execute(GetWorkspaceDefaultRegionDocument, {
|
||||
workspaceId: myThirdWorkspace.id
|
||||
})
|
||||
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
expect(res.data?.workspace.defaultRegion?.key).to.equal(region1Key)
|
||||
})
|
||||
|
||||
it("can't list default region if not workspace admin", async () => {
|
||||
const res = await apollo.execute(
|
||||
GetWorkspaceDefaultRegionDocument,
|
||||
{ workspaceId: myThirdWorkspace.id },
|
||||
{ authUserId: otherGuy.id }
|
||||
)
|
||||
|
||||
expect(res).to.haveGraphQLErrors('You are not authorized')
|
||||
expect(res.data?.workspace.defaultRegion).to.be.not.ok
|
||||
})
|
||||
})
|
||||
|
||||
expect(res).to.not.haveGraphQLErrors()
|
||||
expect(res.data?.workspace.defaultRegion?.key).to.equal(region1Key)
|
||||
})
|
||||
|
||||
it("can't list default region if not workspace admin", async () => {
|
||||
const res = await apollo.execute(
|
||||
GetWorkspaceDefaultRegionDocument,
|
||||
{ workspaceId: myThirdWorkspace.id },
|
||||
{ authUserId: otherGuy.id }
|
||||
)
|
||||
|
||||
expect(res).to.haveGraphQLErrors('You are not authorized')
|
||||
expect(res.data?.workspace.defaultRegion).to.be.not.ok
|
||||
})
|
||||
})
|
||||
})
|
||||
: void 0
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
"dev:clean": "yarn build:clean && yarn dev",
|
||||
"dev:server:test": "cross-env DISABLE_NOTIFICATIONS_CONSUMPTION=true NODE_ENV=test LOG_LEVEL=silent LOG_PRETTY=true node ./bin/ts-www",
|
||||
"test": "cross-env NODE_ENV=test LOG_LEVEL=silent LOG_PRETTY=true mocha",
|
||||
"test:multiregion": "cross-env RUN_TESTS_IN_MULTIREGION_MODE=true yarn test",
|
||||
"test:multiregion": "cross-env RUN_TESTS_IN_MULTIREGION_MODE=true FF_WORKSPACES_MODULE_ENABLED=true FF_WORKSPACES_MULTI_REGION_ENABLED=true yarn test",
|
||||
"test:coverage": "cross-env NODE_ENV=test LOG_LEVEL=silent LOG_PRETTY=true nyc --reporter lcov mocha",
|
||||
"test:report": "yarn test:coverage -- --reporter mocha-junit-reporter --reporter-options mochaFile=reports/test-results.xml",
|
||||
"lint": "yarn lint:tsc && yarn lint:eslint",
|
||||
|
@ -130,11 +130,11 @@
|
|||
"@apollo/rover": "^0.23.0",
|
||||
"@bull-board/express": "^4.2.2",
|
||||
"@faker-js/faker": "^8.4.1",
|
||||
"@graphql-codegen/cli": "^5.0.2",
|
||||
"@graphql-codegen/typed-document-node": "^5.0.7",
|
||||
"@graphql-codegen/typescript": "^4.0.7",
|
||||
"@graphql-codegen/typescript-operations": "^4.2.1",
|
||||
"@graphql-codegen/typescript-resolvers": "^4.1.0",
|
||||
"@graphql-codegen/cli": "^5.0.3",
|
||||
"@graphql-codegen/typed-document-node": "^5.0.11",
|
||||
"@graphql-codegen/typescript": "^4.1.1",
|
||||
"@graphql-codegen/typescript-operations": "^4.3.1",
|
||||
"@graphql-codegen/typescript-resolvers": "^4.4.0",
|
||||
"@parcel/watcher": "^2.4.1",
|
||||
"@swc/core": "^1.2.222",
|
||||
"@tiptap/core": "^2.0.0-beta.176",
|
||||
|
|
|
@ -18,7 +18,6 @@ import type express from 'express'
|
|||
import type net from 'net'
|
||||
import { MaybeAsync, MaybeNullOrUndefined } from '@speckle/shared'
|
||||
import type mocha from 'mocha'
|
||||
import { shouldRunTestsInMultiregionMode } from '@/modules/shared/helpers/envHelper'
|
||||
import {
|
||||
getAvailableRegionKeysFactory,
|
||||
getFreeRegionKeysFactory
|
||||
|
@ -36,6 +35,8 @@ import {
|
|||
initializeRegion
|
||||
} from '@/modules/multiregion/dbSelector'
|
||||
import { Knex } from 'knex'
|
||||
import { isMultiRegionTestMode } from '@/test/speckle-helpers/regions'
|
||||
import { isMultiRegionEnabled } from '@/modules/multiregion/helpers'
|
||||
|
||||
// why is server config only created once!????
|
||||
// because its done in a migration, to not override existing configs
|
||||
|
@ -98,8 +99,9 @@ const setupMultiregionMode = async () => {
|
|||
|
||||
// If not in multi region mode, delete region entries
|
||||
// we only needed them to reset schemas
|
||||
if (!shouldRunTestsInMultiregionMode()) {
|
||||
if (!isMultiRegionTestMode()) {
|
||||
await truncateTables([Regions.name])
|
||||
regionClients = {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,7 +115,8 @@ const unlockFactory = (deps: { db: Knex }) => async () => {
|
|||
export const getRegionKeys = () => Object.keys(regionClients)
|
||||
|
||||
export const resetPubSubFactory = (deps: { db: Knex }) => async () => {
|
||||
if (!shouldRunTestsInMultiregionMode()) {
|
||||
// We wanna reset even outside of multiregion test mode, as long as multi region is generally enabled
|
||||
if (!isMultiRegionEnabled()) {
|
||||
return { drop: async () => {}, reenable: async () => {} }
|
||||
}
|
||||
|
||||
|
@ -138,11 +141,16 @@ export const resetPubSubFactory = (deps: { db: Knex }) => async () => {
|
|||
|
||||
// Drop all subs
|
||||
for (const sub of subscriptions.rows) {
|
||||
await deps.db.raw(`
|
||||
SELECT * FROM aiven_extras.pg_alter_subscription_disable('${sub.subname}');
|
||||
SELECT * FROM aiven_extras.pg_drop_subscription('${sub.subname}');
|
||||
SELECT * FROM aiven_extras.dblink_slot_create_or_drop('${sub.subconninfo}', '${sub.subslotname}', 'drop');
|
||||
`)
|
||||
// Running serially, otherwise some kind of race condition issue can pop up
|
||||
await deps.db.raw(
|
||||
`SELECT * FROM aiven_extras.pg_alter_subscription_disable('${sub.subname}');`
|
||||
)
|
||||
await deps.db.raw(
|
||||
`SELECT * FROM aiven_extras.pg_drop_subscription('${sub.subname}');`
|
||||
)
|
||||
await deps.db.raw(
|
||||
`SELECT * FROM aiven_extras.dblink_slot_create_or_drop('${sub.subconninfo}', '${sub.subslotname}', 'drop');`
|
||||
)
|
||||
}
|
||||
|
||||
// Drop all pubs
|
||||
|
@ -240,7 +248,7 @@ export const initializeTestServer = async (
|
|||
|
||||
export const mochaHooks: mocha.RootHookObject = {
|
||||
beforeAll: async () => {
|
||||
if (shouldRunTestsInMultiregionMode()) {
|
||||
if (isMultiRegionTestMode()) {
|
||||
console.log('Running tests in multi-region mode...')
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import { db } from '@/db/knex'
|
||||
import { isMultiRegionEnabled } from '@/modules/multiregion/helpers'
|
||||
import { Regions } from '@/modules/multiregion/repositories'
|
||||
import {
|
||||
isTestEnv,
|
||||
shouldRunTestsInMultiregionMode
|
||||
} from '@/modules/shared/helpers/envHelper'
|
||||
import { getRegionKeys } from '@/test/hooks'
|
||||
|
||||
/**
|
||||
|
@ -9,3 +14,6 @@ export const truncateRegionsSafely = async () => {
|
|||
const regionKeys = getRegionKeys()
|
||||
await db(Regions.name).whereNotIn(Regions.col.key, regionKeys).delete()
|
||||
}
|
||||
|
||||
export const isMultiRegionTestMode = () =>
|
||||
isMultiRegionEnabled() && isTestEnv() && shouldRunTestsInMultiregionMode()
|
||||
|
|
259
yarn.lock
259
yarn.lock
|
@ -10766,6 +10766,60 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/cli@npm:^5.0.3":
|
||||
version: 5.0.3
|
||||
resolution: "@graphql-codegen/cli@npm:5.0.3"
|
||||
dependencies:
|
||||
"@babel/generator": "npm:^7.18.13"
|
||||
"@babel/template": "npm:^7.18.10"
|
||||
"@babel/types": "npm:^7.18.13"
|
||||
"@graphql-codegen/client-preset": "npm:^4.4.0"
|
||||
"@graphql-codegen/core": "npm:^4.0.2"
|
||||
"@graphql-codegen/plugin-helpers": "npm:^5.0.3"
|
||||
"@graphql-tools/apollo-engine-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/code-file-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/git-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/github-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/graphql-file-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/json-file-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/load": "npm:^8.0.0"
|
||||
"@graphql-tools/prisma-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/url-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/utils": "npm:^10.0.0"
|
||||
"@whatwg-node/fetch": "npm:^0.9.20"
|
||||
chalk: "npm:^4.1.0"
|
||||
cosmiconfig: "npm:^8.1.3"
|
||||
debounce: "npm:^1.2.0"
|
||||
detect-indent: "npm:^6.0.0"
|
||||
graphql-config: "npm:^5.1.1"
|
||||
inquirer: "npm:^8.0.0"
|
||||
is-glob: "npm:^4.0.1"
|
||||
jiti: "npm:^1.17.1"
|
||||
json-to-pretty-yaml: "npm:^1.2.2"
|
||||
listr2: "npm:^4.0.5"
|
||||
log-symbols: "npm:^4.0.0"
|
||||
micromatch: "npm:^4.0.5"
|
||||
shell-quote: "npm:^1.7.3"
|
||||
string-env-interpolation: "npm:^1.0.1"
|
||||
ts-log: "npm:^2.2.3"
|
||||
tslib: "npm:^2.4.0"
|
||||
yaml: "npm:^2.3.1"
|
||||
yargs: "npm:^17.0.0"
|
||||
peerDependencies:
|
||||
"@parcel/watcher": ^2.1.0
|
||||
graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
peerDependenciesMeta:
|
||||
"@parcel/watcher":
|
||||
optional: true
|
||||
bin:
|
||||
gql-gen: cjs/bin.js
|
||||
graphql-code-generator: cjs/bin.js
|
||||
graphql-codegen: cjs/bin.js
|
||||
graphql-codegen-esm: esm/bin.js
|
||||
checksum: 10/c3359668f824246e78656d26af506b5b279d50e08a56f54db87da492bd4d0a8e8b6540a6119402d7f5026c137babfd79e628897c6038e199ee6322f688eec757
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/client-preset@npm:^4.2.2, @graphql-codegen/client-preset@npm:^4.3.0":
|
||||
version: 4.3.0
|
||||
resolution: "@graphql-codegen/client-preset@npm:4.3.0"
|
||||
|
@ -10789,6 +10843,29 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/client-preset@npm:^4.4.0, @graphql-codegen/client-preset@npm:^4.5.0":
|
||||
version: 4.5.0
|
||||
resolution: "@graphql-codegen/client-preset@npm:4.5.0"
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils": "npm:^7.20.2"
|
||||
"@babel/template": "npm:^7.20.7"
|
||||
"@graphql-codegen/add": "npm:^5.0.3"
|
||||
"@graphql-codegen/gql-tag-operations": "npm:4.0.11"
|
||||
"@graphql-codegen/plugin-helpers": "npm:^5.1.0"
|
||||
"@graphql-codegen/typed-document-node": "npm:^5.0.11"
|
||||
"@graphql-codegen/typescript": "npm:^4.1.1"
|
||||
"@graphql-codegen/typescript-operations": "npm:^4.3.1"
|
||||
"@graphql-codegen/visitor-plugin-common": "npm:^5.5.0"
|
||||
"@graphql-tools/documents": "npm:^1.0.0"
|
||||
"@graphql-tools/utils": "npm:^10.0.0"
|
||||
"@graphql-typed-document-node/core": "npm:3.2.0"
|
||||
tslib: "npm:~2.6.0"
|
||||
peerDependencies:
|
||||
graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
checksum: 10/bbbbaa255f6cb1248cd143b54e06f6fc553cdd9f7ca002977bbf42b92cf9d5c6fe052eda1ae1233eab3d50dd80fbb04609bfeeb29132019faead04300e61ddc0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/core@npm:^4.0.2":
|
||||
version: 4.0.2
|
||||
resolution: "@graphql-codegen/core@npm:4.0.2"
|
||||
|
@ -10803,6 +10880,21 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/gql-tag-operations@npm:4.0.11":
|
||||
version: 4.0.11
|
||||
resolution: "@graphql-codegen/gql-tag-operations@npm:4.0.11"
|
||||
dependencies:
|
||||
"@graphql-codegen/plugin-helpers": "npm:^5.1.0"
|
||||
"@graphql-codegen/visitor-plugin-common": "npm:5.5.0"
|
||||
"@graphql-tools/utils": "npm:^10.0.0"
|
||||
auto-bind: "npm:~4.0.0"
|
||||
tslib: "npm:~2.6.0"
|
||||
peerDependencies:
|
||||
graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
checksum: 10/cc277d1af9da611dbd37c00f18d08e8fdc634632c0fba6789a1027931f8e3b925ad64af27a6fa7c23ed44afdef131f9c03025ca9b077cd6e95e5c9823751c6a3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/gql-tag-operations@npm:4.0.7":
|
||||
version: 4.0.7
|
||||
resolution: "@graphql-codegen/gql-tag-operations@npm:4.0.7"
|
||||
|
@ -10847,6 +10939,22 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/plugin-helpers@npm:^5.1.0":
|
||||
version: 5.1.0
|
||||
resolution: "@graphql-codegen/plugin-helpers@npm:5.1.0"
|
||||
dependencies:
|
||||
"@graphql-tools/utils": "npm:^10.0.0"
|
||||
change-case-all: "npm:1.0.15"
|
||||
common-tags: "npm:1.8.2"
|
||||
import-from: "npm:4.0.0"
|
||||
lodash: "npm:~4.17.0"
|
||||
tslib: "npm:~2.6.0"
|
||||
peerDependencies:
|
||||
graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
checksum: 10/415e79be90a1f5d289c9cd7f0a581c277d544be1f7136d7f74f5f067c205eb35fd6cd522455866fa8105f241eec4c77bebe02eef007d5021a7b7a453b85b2001
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/schema-ast@npm:^4.0.2":
|
||||
version: 4.0.2
|
||||
resolution: "@graphql-codegen/schema-ast@npm:4.0.2"
|
||||
|
@ -10860,6 +10968,21 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/typed-document-node@npm:^5.0.11":
|
||||
version: 5.0.11
|
||||
resolution: "@graphql-codegen/typed-document-node@npm:5.0.11"
|
||||
dependencies:
|
||||
"@graphql-codegen/plugin-helpers": "npm:^5.1.0"
|
||||
"@graphql-codegen/visitor-plugin-common": "npm:5.5.0"
|
||||
auto-bind: "npm:~4.0.0"
|
||||
change-case-all: "npm:1.0.15"
|
||||
tslib: "npm:~2.6.0"
|
||||
peerDependencies:
|
||||
graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
checksum: 10/9320fbc9ccf13d0b0ecc7b57f1b0799629ce93a4e0cf95a76cdeb38981e2da92775734daa7bf68a9383e3d01f9a47f4b35cb870aef710f5dc137234b93b9d7cf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/typed-document-node@npm:^5.0.7":
|
||||
version: 5.0.7
|
||||
resolution: "@graphql-codegen/typed-document-node@npm:5.0.7"
|
||||
|
@ -10904,19 +11027,34 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/typescript-resolvers@npm:^4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@graphql-codegen/typescript-resolvers@npm:4.1.0"
|
||||
"@graphql-codegen/typescript-operations@npm:^4.3.1":
|
||||
version: 4.3.1
|
||||
resolution: "@graphql-codegen/typescript-operations@npm:4.3.1"
|
||||
dependencies:
|
||||
"@graphql-codegen/plugin-helpers": "npm:^5.0.4"
|
||||
"@graphql-codegen/typescript": "npm:^4.0.7"
|
||||
"@graphql-codegen/visitor-plugin-common": "npm:5.2.0"
|
||||
"@graphql-codegen/plugin-helpers": "npm:^5.1.0"
|
||||
"@graphql-codegen/typescript": "npm:^4.1.1"
|
||||
"@graphql-codegen/visitor-plugin-common": "npm:5.5.0"
|
||||
auto-bind: "npm:~4.0.0"
|
||||
tslib: "npm:~2.6.0"
|
||||
peerDependencies:
|
||||
graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
checksum: 10/cdad24e16aa9b369e3ef2434032f2527fd1363e82256dd09d2e9aa6d9a55539eeea15665a4289e7695145f7417a9a765ad73979054a97c606d757ee060780819
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/typescript-resolvers@npm:^4.4.0":
|
||||
version: 4.4.0
|
||||
resolution: "@graphql-codegen/typescript-resolvers@npm:4.4.0"
|
||||
dependencies:
|
||||
"@graphql-codegen/plugin-helpers": "npm:^5.1.0"
|
||||
"@graphql-codegen/typescript": "npm:^4.1.1"
|
||||
"@graphql-codegen/visitor-plugin-common": "npm:5.5.0"
|
||||
"@graphql-tools/utils": "npm:^10.0.0"
|
||||
auto-bind: "npm:~4.0.0"
|
||||
tslib: "npm:~2.6.0"
|
||||
peerDependencies:
|
||||
graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
checksum: 10/6ec1e225748d41d925bf1c7d71b868cf24a619c7b6e0e86f887a070f5630f0ff932936b3526cf6347304b58e1a0d365f9aff6d1cf3fbc24684b90d5cb6f852ab
|
||||
checksum: 10/26efe707c89a9612da0ff8be56ebdb91227fe6afb3889b22f49bbec2cd12ba677d58f7946871179dc64c8279acbad773987086fd1c388980c9a7932fd7c15e32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
@ -10935,18 +11073,18 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/typescript@npm:^4.0.9":
|
||||
version: 4.0.9
|
||||
resolution: "@graphql-codegen/typescript@npm:4.0.9"
|
||||
"@graphql-codegen/typescript@npm:^4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@graphql-codegen/typescript@npm:4.1.1"
|
||||
dependencies:
|
||||
"@graphql-codegen/plugin-helpers": "npm:^5.0.4"
|
||||
"@graphql-codegen/plugin-helpers": "npm:^5.1.0"
|
||||
"@graphql-codegen/schema-ast": "npm:^4.0.2"
|
||||
"@graphql-codegen/visitor-plugin-common": "npm:5.3.1"
|
||||
"@graphql-codegen/visitor-plugin-common": "npm:5.5.0"
|
||||
auto-bind: "npm:~4.0.0"
|
||||
tslib: "npm:~2.6.0"
|
||||
peerDependencies:
|
||||
graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
checksum: 10/304026adfe622530b8a2827569dd5bbd390177051be8c214fb79873ec64ef21793635c91657703bfd229a3d06f1a8a6f1addd8ae7eab20d1eff2efe6fb044df7
|
||||
checksum: 10/a47fabef00832122f4981fecbbcfd1e90e2567bdc7fc1d63520b018ae1a6db5217eb42f4f4744265cc492e64cd134b87b7bcfdaddfd7b3e35ce5c47d4548225d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
@ -10970,11 +11108,11 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/visitor-plugin-common@npm:5.3.1":
|
||||
version: 5.3.1
|
||||
resolution: "@graphql-codegen/visitor-plugin-common@npm:5.3.1"
|
||||
"@graphql-codegen/visitor-plugin-common@npm:5.5.0, @graphql-codegen/visitor-plugin-common@npm:^5.5.0":
|
||||
version: 5.5.0
|
||||
resolution: "@graphql-codegen/visitor-plugin-common@npm:5.5.0"
|
||||
dependencies:
|
||||
"@graphql-codegen/plugin-helpers": "npm:^5.0.4"
|
||||
"@graphql-codegen/plugin-helpers": "npm:^5.1.0"
|
||||
"@graphql-tools/optimize": "npm:^2.0.0"
|
||||
"@graphql-tools/relay-operation-optimizer": "npm:^7.0.0"
|
||||
"@graphql-tools/utils": "npm:^10.0.0"
|
||||
|
@ -10986,7 +11124,7 @@ __metadata:
|
|||
tslib: "npm:~2.6.0"
|
||||
peerDependencies:
|
||||
graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
checksum: 10/6dd0464d9099d5aeabeb766515fc8dd2fc84bcae4cb0e3653d7f38aea716d6622d35d7cbb57a1954e6bc1cde10f4dd8c4a75ceb4e8bb8cdbba16219615666a5f
|
||||
checksum: 10/f923c40ae996a2accf3a951d302b3da9b3c063f4b1c66b159bf3f74910e18ea592e87b3f35495a84f6c36d1198d880dd07f6e8c3fe94b0d6dba0f2f77522cb5d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
@ -16611,11 +16749,11 @@ __metadata:
|
|||
"@datadog/browser-rum": "npm:^5.11.0"
|
||||
"@datadog/datadog-ci": "npm:^2.37.0"
|
||||
"@eslint/config-inspector": "npm:^0.4.10"
|
||||
"@graphql-codegen/cli": "npm:^5.0.2"
|
||||
"@graphql-codegen/client-preset": "npm:^4.3.0"
|
||||
"@graphql-codegen/plugin-helpers": "npm:^5.0.4"
|
||||
"@graphql-codegen/typescript": "npm:^4.0.9"
|
||||
"@graphql-codegen/visitor-plugin-common": "npm:5.3.1"
|
||||
"@graphql-codegen/cli": "npm:^5.0.3"
|
||||
"@graphql-codegen/client-preset": "npm:^4.5.0"
|
||||
"@graphql-codegen/plugin-helpers": "npm:^5.1.0"
|
||||
"@graphql-codegen/typescript": "npm:^4.1.1"
|
||||
"@graphql-codegen/visitor-plugin-common": "npm:5.5.0"
|
||||
"@headlessui/vue": "npm:^1.7.13"
|
||||
"@heroicons/vue": "npm:^2.0.12"
|
||||
"@jsonforms/core": "npm:^3.3.0"
|
||||
|
@ -16949,11 +17087,11 @@ __metadata:
|
|||
"@bull-board/express": "npm:^4.2.2"
|
||||
"@faker-js/faker": "npm:^8.4.1"
|
||||
"@godaddy/terminus": "npm:^4.9.0"
|
||||
"@graphql-codegen/cli": "npm:^5.0.2"
|
||||
"@graphql-codegen/typed-document-node": "npm:^5.0.7"
|
||||
"@graphql-codegen/typescript": "npm:^4.0.7"
|
||||
"@graphql-codegen/typescript-operations": "npm:^4.2.1"
|
||||
"@graphql-codegen/typescript-resolvers": "npm:^4.1.0"
|
||||
"@graphql-codegen/cli": "npm:^5.0.3"
|
||||
"@graphql-codegen/typed-document-node": "npm:^5.0.11"
|
||||
"@graphql-codegen/typescript": "npm:^4.1.1"
|
||||
"@graphql-codegen/typescript-operations": "npm:^4.3.1"
|
||||
"@graphql-codegen/typescript-resolvers": "npm:^4.4.0"
|
||||
"@graphql-tools/mock": "npm:^9.0.4"
|
||||
"@graphql-tools/schema": "npm:^10.0.6"
|
||||
"@lifeomic/attempt": "npm:^3.1.0"
|
||||
|
@ -23601,6 +23739,16 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@whatwg-node/fetch@npm:^0.9.20":
|
||||
version: 0.9.23
|
||||
resolution: "@whatwg-node/fetch@npm:0.9.23"
|
||||
dependencies:
|
||||
"@whatwg-node/node-fetch": "npm:^0.6.0"
|
||||
urlpattern-polyfill: "npm:^10.0.0"
|
||||
checksum: 10/6024a3fcc2175de6a20ea4833c009d0488cf68c01cd235541ec0dba0ce59bb0b0befcd4cd788db0e65b99a5a8755bc00d490dc9d7beeb0c2f35058ef46732fe0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@whatwg-node/node-fetch@npm:^0.3.6":
|
||||
version: 0.3.6
|
||||
resolution: "@whatwg-node/node-fetch@npm:0.3.6"
|
||||
|
@ -23627,6 +23775,18 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@whatwg-node/node-fetch@npm:^0.6.0":
|
||||
version: 0.6.0
|
||||
resolution: "@whatwg-node/node-fetch@npm:0.6.0"
|
||||
dependencies:
|
||||
"@kamilkisiela/fast-url-parser": "npm:^1.1.4"
|
||||
busboy: "npm:^1.6.0"
|
||||
fast-querystring: "npm:^1.1.1"
|
||||
tslib: "npm:^2.6.3"
|
||||
checksum: 10/87ad7c4cc68b24499089166617d16cbe25d9107b4d9354c804232f8c53c4fc27d1e2166471d878390442620e09588aa1d8705a8e2ea5bcc2d728a558ad1156c3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@wry/caches@npm:^1.0.0":
|
||||
version: 1.0.1
|
||||
resolution: "@wry/caches@npm:1.0.1"
|
||||
|
@ -33572,6 +33732,31 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"graphql-config@npm:^5.1.1":
|
||||
version: 5.1.3
|
||||
resolution: "graphql-config@npm:5.1.3"
|
||||
dependencies:
|
||||
"@graphql-tools/graphql-file-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/json-file-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/load": "npm:^8.0.0"
|
||||
"@graphql-tools/merge": "npm:^9.0.0"
|
||||
"@graphql-tools/url-loader": "npm:^8.0.0"
|
||||
"@graphql-tools/utils": "npm:^10.0.0"
|
||||
cosmiconfig: "npm:^8.1.0"
|
||||
jiti: "npm:^2.0.0"
|
||||
minimatch: "npm:^9.0.5"
|
||||
string-env-interpolation: "npm:^1.0.1"
|
||||
tslib: "npm:^2.4.0"
|
||||
peerDependencies:
|
||||
cosmiconfig-toml-loader: ^1.0.0
|
||||
graphql: ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
peerDependenciesMeta:
|
||||
cosmiconfig-toml-loader:
|
||||
optional: true
|
||||
checksum: 10/9d37f5d424f302808102d118988878be5e4841ba1a06a865cdb9052b24e26eaa9923fb18163bf4f32102d87b3895c53e2ffcdebc1d651f04b56f93f5c38b83c3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"graphql-redis-subscriptions@npm:^2.2.2":
|
||||
version: 2.4.2
|
||||
resolution: "graphql-redis-subscriptions@npm:2.4.2"
|
||||
|
@ -36695,6 +36880,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jiti@npm:^2.0.0":
|
||||
version: 2.4.0
|
||||
resolution: "jiti@npm:2.4.0"
|
||||
bin:
|
||||
jiti: lib/jiti-cli.mjs
|
||||
checksum: 10/10aa999a4f9bccc82b1dab9ebaf4484a8770450883c1bf7fafc07f8fca1e417fd8e7731e651337d1060c9e2ff3f97362dcdfd27e86d1f385db97f4adf7b5a21d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jiti@npm:^2.1.2":
|
||||
version: 2.3.3
|
||||
resolution: "jiti@npm:2.3.3"
|
||||
|
@ -39184,6 +39378,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minimatch@npm:^9.0.5":
|
||||
version: 9.0.5
|
||||
resolution: "minimatch@npm:9.0.5"
|
||||
dependencies:
|
||||
brace-expansion: "npm:^2.0.1"
|
||||
checksum: 10/dd6a8927b063aca6d910b119e1f2df6d2ce7d36eab91de83167dd136bb85e1ebff97b0d3de1cb08bd1f7e018ca170b4962479fefab5b2a69e2ae12cb2edc8348
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minimatch@npm:~3.0.3, minimatch@npm:~3.0.4":
|
||||
version: 3.0.8
|
||||
resolution: "minimatch@npm:3.0.8"
|
||||
|
|
Загрузка…
Ссылка в новой задаче