@contember/engine-server 1.1.0-rc.0 → 1.2.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/dist/src/MasterContainer.d.ts +12 -25
  2. package/dist/src/MasterContainer.d.ts.map +1 -1
  3. package/dist/src/MasterContainer.js +36 -80
  4. package/dist/src/MasterContainer.js.map +1 -1
  5. package/dist/src/bootstrap/Initializer.d.ts +3 -3
  6. package/dist/src/bootstrap/Initializer.d.ts.map +1 -1
  7. package/dist/src/bootstrap/Initializer.js +15 -6
  8. package/dist/src/bootstrap/Initializer.js.map +1 -1
  9. package/dist/src/config/config.d.ts +5 -4
  10. package/dist/src/config/config.d.ts.map +1 -1
  11. package/dist/src/config/config.js +2 -2
  12. package/dist/src/config/config.js.map +1 -1
  13. package/dist/src/config/configSchema.d.ts +45 -11
  14. package/dist/src/config/configSchema.d.ts.map +1 -1
  15. package/dist/src/config/configSchema.js +20 -18
  16. package/dist/src/config/configSchema.js.map +1 -1
  17. package/dist/src/config/configTemplate.d.ts.map +1 -1
  18. package/dist/src/config/configTemplate.js +23 -0
  19. package/dist/src/config/configTemplate.js.map +1 -1
  20. package/dist/src/index.d.ts +9 -3
  21. package/dist/src/index.d.ts.map +1 -1
  22. package/dist/src/index.js +23 -4
  23. package/dist/src/index.js.map +1 -1
  24. package/dist/src/project/ProjectContainer.js +1 -1
  25. package/dist/src/project/ProjectContainer.js.map +1 -1
  26. package/dist/src/project/ProjectContainerResolver.d.ts +6 -3
  27. package/dist/src/project/ProjectContainerResolver.d.ts.map +1 -1
  28. package/dist/src/project/ProjectContainerResolver.js +4 -16
  29. package/dist/src/project/ProjectContainerResolver.js.map +1 -1
  30. package/dist/src/projectGroup/ProjectGroupContainer.d.ts +2 -1
  31. package/dist/src/projectGroup/ProjectGroupContainer.d.ts.map +1 -1
  32. package/dist/src/projectGroup/ProjectGroupContainer.js +8 -4
  33. package/dist/src/projectGroup/ProjectGroupContainer.js.map +1 -1
  34. package/dist/src/projectGroup/ProjectGroupResolver.d.ts +7 -0
  35. package/dist/src/projectGroup/ProjectGroupResolver.d.ts.map +1 -0
  36. package/dist/src/projectGroup/ProjectGroupResolver.js +13 -0
  37. package/dist/src/projectGroup/ProjectGroupResolver.js.map +1 -0
  38. package/dist/src/start.js +15 -112
  39. package/dist/src/start.js.map +1 -1
  40. package/dist/src/tsconfig.tsbuildinfo +1 -1
  41. package/dist/src/utils/index.d.ts +0 -4
  42. package/dist/src/utils/index.d.ts.map +1 -1
  43. package/dist/src/utils/index.js +0 -4
  44. package/dist/src/utils/index.js.map +1 -1
  45. package/dist/src/utils/serverStartup.d.ts +31 -0
  46. package/dist/src/utils/serverStartup.d.ts.map +1 -0
  47. package/dist/src/utils/serverStartup.js +42 -0
  48. package/dist/src/utils/serverStartup.js.map +1 -0
  49. package/dist/src/utils/serverTermination.d.ts +12 -0
  50. package/dist/src/utils/serverTermination.d.ts.map +1 -0
  51. package/dist/src/utils/serverTermination.js +26 -0
  52. package/dist/src/utils/serverTermination.js.map +1 -0
  53. package/dist/tests/e2e/http.test.js +74 -157
  54. package/dist/tests/e2e/http.test.js.map +1 -1
  55. package/dist/tests/e2e/requests.d.ts +4 -0
  56. package/dist/tests/e2e/requests.d.ts.map +1 -0
  57. package/dist/tests/e2e/requests.js +59 -0
  58. package/dist/tests/e2e/requests.js.map +1 -0
  59. package/dist/tests/e2e/tester.d.ts +19 -0
  60. package/dist/tests/e2e/tester.d.ts.map +1 -0
  61. package/dist/tests/e2e/tester.js +99 -0
  62. package/dist/tests/e2e/tester.js.map +1 -0
  63. package/dist/tests/tsconfig.tsbuildinfo +1 -1
  64. package/esbuild.js +7 -14
  65. package/package.json +20 -20
  66. package/src/MasterContainer.ts +58 -110
  67. package/src/bootstrap/Initializer.ts +16 -6
  68. package/src/config/config.ts +7 -5
  69. package/src/config/configSchema.ts +30 -33
  70. package/src/config/configTemplate.ts +23 -0
  71. package/src/index.ts +15 -2
  72. package/src/project/ProjectContainer.ts +1 -1
  73. package/src/project/ProjectContainerResolver.ts +4 -17
  74. package/src/projectGroup/ProjectGroupContainer.ts +12 -3
  75. package/src/projectGroup/ProjectGroupResolver.ts +12 -0
  76. package/src/start.ts +18 -127
  77. package/src/utils/index.ts +0 -4
  78. package/src/utils/serverStartup.ts +42 -0
  79. package/src/utils/serverTermination.ts +20 -0
  80. package/tests/e2e/http.test.ts +84 -150
  81. package/tests/e2e/requests.ts +71 -0
  82. package/tests/e2e/tester.ts +118 -0
  83. package/alpine.dockerfile +0 -30
  84. package/debian.dockerfile +0 -27
  85. package/dist/src/http/CollectHttpMetricsMiddelware.d.ts +0 -5
  86. package/dist/src/http/CollectHttpMetricsMiddelware.d.ts.map +0 -1
  87. package/dist/src/http/CollectHttpMetricsMiddelware.js +0 -36
  88. package/dist/src/http/CollectHttpMetricsMiddelware.js.map +0 -1
  89. package/dist/src/http/ShowMetricsMiddleware.d.ts +0 -4
  90. package/dist/src/http/ShowMetricsMiddleware.d.ts.map +0 -1
  91. package/dist/src/http/ShowMetricsMiddleware.js +0 -20
  92. package/dist/src/http/ShowMetricsMiddleware.js.map +0 -1
  93. package/dist/src/http/index.d.ts +0 -3
  94. package/dist/src/http/index.d.ts.map +0 -1
  95. package/dist/src/http/index.js +0 -19
  96. package/dist/src/http/index.js.map +0 -1
  97. package/dist/src/projectGroup/ProjectGroupContainerResolver.d.ts +0 -12
  98. package/dist/src/projectGroup/ProjectGroupContainerResolver.d.ts.map +0 -1
  99. package/dist/src/projectGroup/ProjectGroupContainerResolver.js +0 -36
  100. package/dist/src/projectGroup/ProjectGroupContainerResolver.js.map +0 -1
  101. package/dist/src/utils/cluster.d.ts +0 -9
  102. package/dist/src/utils/cluster.d.ts.map +0 -1
  103. package/dist/src/utils/cluster.js +0 -47
  104. package/dist/src/utils/cluster.js.map +0 -1
  105. package/dist/src/utils/dbMetrics.d.ts +0 -16
  106. package/dist/src/utils/dbMetrics.d.ts.map +0 -1
  107. package/dist/src/utils/dbMetrics.js +0 -98
  108. package/dist/src/utils/dbMetrics.js.map +0 -1
  109. package/dist/src/utils/timeout.d.ts +0 -2
  110. package/dist/src/utils/timeout.d.ts.map +0 -1
  111. package/dist/src/utils/timeout.js +0 -8
  112. package/dist/src/utils/timeout.js.map +0 -1
  113. package/dist/src/utils/tuple.d.ts +0 -2
  114. package/dist/src/utils/tuple.d.ts.map +0 -1
  115. package/dist/src/utils/tuple.js +0 -6
  116. package/dist/src/utils/tuple.js.map +0 -1
  117. package/jasmine.json +0 -7
  118. package/src/http/CollectHttpMetricsMiddelware.ts +0 -32
  119. package/src/http/ShowMetricsMiddleware.ts +0 -12
  120. package/src/http/index.ts +0 -2
  121. package/src/projectGroup/ProjectGroupContainerResolver.ts +0 -47
  122. package/src/utils/cluster.ts +0 -40
  123. package/src/utils/dbMetrics.ts +0 -111
  124. package/src/utils/timeout.ts +0 -4
  125. package/src/utils/tuple.ts +0 -1
  126. package/tslint.json +0 -14
@@ -6,6 +6,8 @@ import {
6
6
  Authenticator,
7
7
  CryptoWrapper,
8
8
  ProjectGroupContainer,
9
+ ProjectMembershipFetcher,
10
+ ProjectMembershipResolver,
9
11
  Providers,
10
12
  SystemGraphQLHandlerFactory,
11
13
  TenantGraphQLHandlerFactory,
@@ -30,6 +32,7 @@ interface ProjectGroupContainerFactoryArgs
30
32
 
31
33
  export class ProjectGroupContainerFactory {
32
34
  constructor(
35
+ private readonly debug: boolean,
33
36
  private readonly providers: Providers,
34
37
  private readonly systemContainerFactory: SystemContainerFactory,
35
38
  private readonly tenantContainerFactory: TenantContainerFactory,
@@ -45,13 +48,15 @@ export class ProjectGroupContainerFactory {
45
48
  slug)
46
49
  .addService('providers', () =>
47
50
  this.providers)
48
- .addService('tenantConnection', (): Connection.ConnectionType =>
49
- new Connection(config.db, {}))
51
+ .addService('tenantDbCredentials', () =>
52
+ config.db)
53
+ .addService('tenantConnection', ({ tenantDbCredentials }): Connection.ConnectionType =>
54
+ Connection.create(tenantDbCredentials, {}))
50
55
  .addService('projectSchemaResolver', () =>
51
56
  new ProjectSchemaResolverProxy())
52
57
  .addService('projectInitializer', () =>
53
58
  new ProjectInitializerProxy())
54
- .addService('tenantContainer', ({ tenantConnection, projectSchemaResolver, projectInitializer }) => {
59
+ .addService('tenantContainer', ({ tenantConnection, tenantDbCredentials, projectSchemaResolver, projectInitializer }) => {
55
60
  const encryptionKey = config.secrets.encryptionKey
56
61
  ? createSecretKey(Buffer.from(config.secrets.encryptionKey, 'hex'))
57
62
  : undefined
@@ -59,6 +64,7 @@ export class ProjectGroupContainerFactory {
59
64
  const cryptoWrapper = new CryptoWrapper(encryptionKey)
60
65
  return this.tenantContainerFactory.create({
61
66
  connection: tenantConnection,
67
+ dbCredentials: tenantDbCredentials,
62
68
  mailOptions: config.mailer,
63
69
  tenantCredentials: config.credentials,
64
70
  projectInitializer,
@@ -103,6 +109,8 @@ export class ProjectGroupContainerFactory {
103
109
  this.systemGraphQLHandlerFactory.create(systemContainer.systemResolversFactory))
104
110
  .addService('authenticator', ({ tenantDatabase, tenantContainer }) =>
105
111
  new Authenticator(tenantDatabase, tenantContainer.apiKeyManager))
112
+ .addService('projectMembershipResolver', ({ tenantContainer }) =>
113
+ new ProjectMembershipResolver(this.debug, new ProjectMembershipFetcher(tenantContainer.projectMemberManager, tenantContainer.databaseContext)))
106
114
  .build()
107
115
  .pick(
108
116
  'projectContainerResolver',
@@ -113,6 +121,7 @@ export class ProjectGroupContainerFactory {
113
121
  'tenantContainer',
114
122
  'tenantDatabase',
115
123
  'authenticator',
124
+ 'projectMembershipResolver',
116
125
  'tenantGraphQLHandler',
117
126
  'slug',
118
127
  )
@@ -0,0 +1,12 @@
1
+ import { ProjectGroupContainer, ProjectGroupResolver as ProjectGroupResolverInterface } from '@contember/engine-http'
2
+
3
+ export class ProjectGroupResolver implements ProjectGroupResolverInterface {
4
+ constructor(
5
+ private readonly projectGroupContainer: ProjectGroupContainer,
6
+ ) {
7
+ }
8
+
9
+ resolveContainer(): Promise<ProjectGroupContainer> {
10
+ return Promise.resolve(this.projectGroupContainer)
11
+ }
12
+ }
package/src/start.ts CHANGED
@@ -1,85 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { createContainer, readConfig } from './index'
4
- import { Server } from 'net'
3
+ import { createContainer, listenOnProcessTermination, serverConfigSchema, TerminationJob } from './index'
5
4
  import loadPlugins from './loadPlugins'
6
- import { ConfigProcessor } from '@contember/engine-plugins'
7
- import { join } from 'path'
8
- import os from 'os'
9
- import cluster from 'cluster'
10
- import { getClusterProcessType, initSentry, notifyWorkerStarted, timeout, waitForWorker } from './utils'
11
- import { ConfigSource } from './config/config'
5
+ import { initSentry } from './utils'
12
6
 
13
- const createServerTerminator = (): Server[] => {
14
- const signals = [
15
- ['SIGHUP', 1],
16
- ['SIGINT', 2],
17
- ['SIGTERM', 15],
18
- ] as const
19
7
 
20
- let servers: Server[] = []
21
- for (const [signal, code] of signals) {
22
- process.on(signal, async () => {
23
- // eslint-disable-next-line no-console
24
- console.log(`process received a ${signal} signal`)
25
- for (const server of servers) {
26
- await new Promise(resolve => server.close(() => resolve(null)))
27
- }
28
- process.exit(128 + code)
29
- })
30
- }
31
- return servers
32
- }
8
+ import { getServerVersion, isDebugMode, printStartInfo, resolveServerConfig } from './utils/serverStartup'
33
9
 
34
- ;(async () => {
10
+ (async () => {
35
11
  if (process.env.NODE_HEAPDUMP === 'true') {
36
12
  // eslint-disable-next-line no-console
37
13
  console.log('Initializing heapdump')
38
14
  await import('heapdump')
39
15
  }
40
- const isDebug = process.env.NODE_ENV === 'development'
41
- const packageJsonFile = process.env.CONTEMBER_PACKAGE_JSON || join(__dirname, '../../package.json')
42
- // eslint-disable-next-line @typescript-eslint/no-var-requires
43
- const version = require(packageJsonFile).version
44
-
45
- if (cluster.isMaster) {
46
- // eslint-disable-next-line no-console
47
- console.log(`Starting Contember ${version}`)
48
- // eslint-disable-next-line no-console
49
- console.log(`NODE_ENV is set to ${process.env.NODE_ENV}`)
50
- if (isDebug) {
51
- // eslint-disable-next-line no-console
52
- console.log('Starting Contember in debug mode')
53
- // eslint-disable-next-line no-console
54
- console.log('NEVER USE debug mode in production environment')
55
- }
56
- }
16
+ const terminationJobs: TerminationJob[] = []
17
+ listenOnProcessTermination(terminationJobs)
57
18
 
19
+ const isDebug = isDebugMode()
20
+ const version = getServerVersion()
21
+ printStartInfo({ version, isDebug })
58
22
  const plugins = await loadPlugins()
59
- const configProcessors = plugins
60
- .map(it => (it.getConfigProcessor ? it.getConfigProcessor() : null))
61
- .filter((it): it is ConfigProcessor => it !== null)
62
-
63
- const configSources: ConfigSource[] = []
64
- for (const configType of ['file', 'yaml', 'json'] as const) {
65
- const envValue = process.env['CONTEMBER_CONFIG_' + configType.toUpperCase()]
66
- if (envValue) {
67
- configSources.push({ type: configType, data: envValue })
68
- }
69
- }
70
-
71
- const { serverConfig, projectConfigResolver, tenantConfigResolver } = await readConfig(configSources, configProcessors)
72
-
73
- if (process.argv[2] === 'validate') {
74
- process.exit(0)
75
- }
76
- 'sentry' in serverConfig.logging && initSentry(serverConfig.logging.sentry?.dsn)
77
- const workerConfig = serverConfig.workerCount || 1
23
+ const { serverConfig, projectConfigResolver, tenantConfigResolver } = await resolveServerConfig({ plugins, serverConfigSchema })
78
24
 
79
- const workerCount = workerConfig === 'auto' ? os.cpus().length : Number(workerConfig)
80
- const isClusterMode = workerCount > 1
81
-
82
- const processType = getClusterProcessType(isClusterMode)
25
+ initSentry(serverConfig.logging.sentry?.dsn)
83
26
 
84
27
  const container = createContainer({
85
28
  debugMode: isDebug,
@@ -87,70 +30,18 @@ const createServerTerminator = (): Server[] => {
87
30
  projectConfigResolver,
88
31
  tenantConfigResolver,
89
32
  plugins,
90
- processType,
91
33
  version,
92
34
  })
93
35
 
94
- let initializedProjects: string[] = []
95
- if (cluster.isMaster) {
96
- const monitoringPort = serverConfig.monitoringPort
97
- const terminator = createServerTerminator()
98
- terminator.push(
99
- container.monitoringKoa.listen(monitoringPort, () => {
100
- // eslint-disable-next-line no-console
101
- console.log(`Monitoring running on http://localhost:${monitoringPort}`)
102
- }),
103
- )
104
- if (!serverConfig.projectGroup) {
105
- initializedProjects = await container.initializer.initialize()
106
- }
107
- }
36
+ const initializedProjects: string[] = await container.initializer.initialize()
108
37
 
109
- const port = serverConfig.port
110
- const printStarted = () => {
111
- if (serverConfig.projectGroup) {
112
- // eslint-disable-next-line no-console
113
- console.log('Contember Cloud running')
114
- } else {
115
- // eslint-disable-next-line no-console
116
- console.log(`Contember API running on http://localhost:${port}`)
117
- // eslint-disable-next-line no-console
118
- console.log(initializedProjects.length ? `Initialized projects: ${initializedProjects.join(', ')}` : 'No project initialized')
119
- }
120
- }
121
-
122
- if (isClusterMode && cluster.isMaster) {
38
+ const httpServer = container.koa.listen(serverConfig.port, () => {
123
39
  // eslint-disable-next-line no-console
124
- console.log(`Master ${process.pid} is running`)
125
- for (let i = 0; i < workerCount; i++) {
126
- cluster.fork()
127
- await waitForWorker(15000)
128
- }
129
- printStarted()
130
-
131
- cluster.on('exit', async (worker, code, signal) => {
132
- // eslint-disable-next-line no-console
133
- console.log(`Worker ${worker.process.pid} died with signal ${signal}, restarting`)
134
- await timeout(2000)
135
- cluster.fork()
136
- })
137
- } else {
138
- if (isClusterMode) {
139
- // eslint-disable-next-line no-console
140
- console.log(`Starting worker ${process.pid}`)
141
- }
142
- const serverTerminator = createServerTerminator()
143
-
144
- serverTerminator.push(
145
- container.koa.listen(port, () => {
146
- if (!isClusterMode) {
147
- printStarted()
148
- } else {
149
- notifyWorkerStarted()
150
- }
151
- }),
152
- )
153
- }
40
+ console.log(`Contember API running on http://localhost:${(serverConfig.port)}`)
41
+ // eslint-disable-next-line no-console
42
+ console.log(initializedProjects.length ? `Initialized projects: ${initializedProjects.join(', ')}` : 'No project initialized')
43
+ })
44
+ terminationJobs.push(() => new Promise<any>(resolve => httpServer.close(resolve)))
154
45
  })().catch(e => {
155
46
  // eslint-disable-next-line no-console
156
47
  console.log(e)
@@ -1,6 +1,2 @@
1
- export * from './cluster'
2
- export * from './dbMetrics'
3
1
  export * from './sentry'
4
2
  export * from './strings'
5
- export * from './timeout'
6
- export * from './tuple'
@@ -0,0 +1,42 @@
1
+ import { join } from 'path'
2
+ import { ConfigProcessor, Plugin } from '@contember/engine-plugins'
3
+ import { ConfigSource, readConfig, ServerConfig } from '../config/config'
4
+ import { Type } from '@contember/typesafe'
5
+
6
+ export const getServerVersion = (): string => {
7
+ const packageJsonFile = process.env.CONTEMBER_PACKAGE_JSON || join(__dirname, '../../../package.json')
8
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
9
+ const version = require(packageJsonFile).version
10
+ return version
11
+ }
12
+
13
+ export const isDebugMode = (): boolean => process.env.NODE_ENV === 'development'
14
+
15
+ export const printStartInfo = ({ version, isDebug }: { version: string; isDebug: boolean }) => {
16
+ // eslint-disable-next-line no-console
17
+ console.log(`Starting Contember ${version}`)
18
+ // eslint-disable-next-line no-console
19
+ console.log(`NODE_ENV is set to ${process.env.NODE_ENV}`)
20
+ if (isDebug) {
21
+ // eslint-disable-next-line no-console
22
+ console.log('Starting Contember in debug mode')
23
+ // eslint-disable-next-line no-console
24
+ console.log('NEVER USE debug mode in production environment')
25
+ }
26
+ }
27
+
28
+ export const resolveServerConfig = async <T extends ServerConfig>({ plugins, serverConfigSchema }: { plugins: Plugin[]; serverConfigSchema: Type<T> }) => {
29
+ const configProcessors = plugins
30
+ .map(it => (it.getConfigProcessor ? it.getConfigProcessor() : null))
31
+ .filter((it): it is ConfigProcessor => it !== null)
32
+
33
+ const configSources: ConfigSource[] = []
34
+ for (const configType of ['file', 'yaml', 'json'] as const) {
35
+ const envValue = process.env['CONTEMBER_CONFIG_' + configType.toUpperCase()]
36
+ if (envValue) {
37
+ configSources.push({ type: configType, data: envValue })
38
+ }
39
+ }
40
+
41
+ return await readConfig(configSources, configProcessors, serverConfigSchema)
42
+ }
@@ -0,0 +1,20 @@
1
+ import cluster from 'cluster'
2
+
3
+ const signals = {
4
+ SIGHUP: 1,
5
+ SIGINT: 2,
6
+ SIGTERM: 15,
7
+ }
8
+ export type TerminationJob = (args: { signal: keyof typeof signals; code: number }) => Promise<void>
9
+ export const listenOnProcessTermination = (jobs: TerminationJob[]) => {
10
+ for (const [signal, code] of Object.entries(signals)) {
11
+ process.on(signal, async () => {
12
+ // eslint-disable-next-line no-console
13
+ console.log(`Process ${process.pid} received a ${signal} signal, executing ${jobs.length} termination jobs`)
14
+ await Promise.allSettled(jobs.map(it => it({ signal: signal as keyof typeof signals, code })))
15
+ // eslint-disable-next-line no-console
16
+ console.log(cluster.isMaster ? `All terminated, exiting` : 'All terminated, exiting a worker')
17
+ process.exit(128 + code)
18
+ })
19
+ }
20
+ }
@@ -1,113 +1,10 @@
1
1
  import supertest from 'supertest'
2
- import * as nodeAssert from 'assert'
3
- import { assert, beforeAll, test } from 'vitest'
4
- import { MigrationFilesManager, MigrationsResolver } from '@contember/schema-migrations'
5
- import { getExampleProjectDirectory } from '@contember/engine-api-tester'
2
+ import { assert, test } from 'vitest'
3
+ import { createSchema, SchemaDefinition as def } from '@contember/schema-definition'
4
+ import { apiUrl, createTester, gql } from './tester'
5
+ import { addProjectMember, signIn, signUp } from './requests'
6
6
 
7
7
 
8
- const rootToken = String(process.env.CONTEMBER_ROOT_TOKEN)
9
- const loginToken = String(process.env.CONTEMBER_LOGIN_TOKEN)
10
- const apiUrl = String(process.env.CONTEMBER_API_URL)
11
- let projectSlug = ''
12
-
13
- // Used to allow prettier formatting of GraphQL queries
14
- const gql = (strings: TemplateStringsArray) => {
15
- nodeAssert.strictEqual(strings.length, 1)
16
- return strings[0]
17
- }
18
-
19
-
20
- const executeGraphql = (
21
- query: string,
22
- options: { authorizationToken?: string; path?: string; variables?: Record<string, any> } = {},
23
- ) => {
24
- return supertest(apiUrl)
25
- .post(options.path || `/content/${projectSlug}/live`)
26
- .set('Authorization', 'Bearer ' + (options.authorizationToken || rootToken))
27
- .send({
28
- query,
29
- variables: options.variables || {},
30
- })
31
- }
32
-
33
- const signIn = async (email: string, password: string): Promise<string> => {
34
- const response2 = await executeGraphql(
35
- gql`
36
- mutation ($email: String!, $password: String!) {
37
- signIn(email: $email, password: $password) {
38
- ok
39
- result {
40
- token
41
- }
42
- }
43
- }
44
- `,
45
- {
46
- variables: { email, password },
47
- authorizationToken: loginToken,
48
- path: '/tenant',
49
- },
50
- )
51
-
52
- return response2.body.data.signIn.result.token
53
- }
54
-
55
- beforeAll(async () => {
56
- projectSlug = 'test' + Date.now()
57
- const result = await executeGraphql(`
58
- mutation($projectSlug: String!, $config: Json!) {
59
- createProject(projectSlug: $projectSlug, config: $config) {
60
- ok
61
- error { code }
62
- }
63
- }
64
- `,
65
- {
66
- path: '/tenant',
67
- variables: {
68
- projectSlug: projectSlug,
69
- config: {},
70
- },
71
- })
72
- .expect(response => {
73
- assert.isOk(response.body)
74
- assert.isOk(response.body.data)
75
- assert.isOk(response.body.data.createProject)
76
- if (!response.body.data.createProject.ok) {
77
- assert.equal(response.body.data.createProject.error.code, 'ALREADY_EXISTS')
78
- }
79
- })
80
- .expect(200)
81
- if (!result.body.data.createProject.ok) {
82
- return
83
- }
84
- const migrationFilesManager = new MigrationsResolver(MigrationFilesManager.createForProject(getExampleProjectDirectory(), 'sample'))
85
- const migrations = await migrationFilesManager.getMigrations()
86
- assert.isAtLeast(migrations.length, 2)
87
- await executeGraphql(`
88
- mutation($migrations: [Migration!]!) {
89
- migrate(migrations: $migrations) {
90
- ok
91
- error { code }
92
- }
93
- }
94
- `,
95
- {
96
- path: '/system/' + projectSlug,
97
- variables: {
98
- migrations,
99
- },
100
- })
101
- .expect(response => {
102
- assert.deepStrictEqual(response.body.data, {
103
- migrate: {
104
- error: null,
105
- ok: true,
106
- },
107
- })
108
- })
109
- })
110
-
111
8
  test('show homepage', async () => {
112
9
  await supertest(apiUrl)
113
10
  .get('/')
@@ -115,8 +12,16 @@ test('show homepage', async () => {
115
12
  .expect('App is running')
116
13
  })
117
14
 
15
+ namespace TagModel {
16
+ export class Tag {
17
+ label = def.stringColumn()
18
+ }
19
+ }
20
+
118
21
  test('Content API: create & read tag', async () => {
119
- await executeGraphql(
22
+ const tester = await createTester(createSchema(TagModel))
23
+
24
+ await tester(
120
25
  gql`
121
26
  mutation {
122
27
  createTag(data: { label: "graphql" }) {
@@ -134,7 +39,7 @@ test('Content API: create & read tag', async () => {
134
39
  })
135
40
  .expect(200)
136
41
 
137
- await executeGraphql(
42
+ await tester(
138
43
  gql`
139
44
  query {
140
45
  listTag(filter: { label: { eq: "graphql" } }) {
@@ -156,7 +61,10 @@ test('Content API: create & read tag', async () => {
156
61
  })
157
62
 
158
63
  test('Content API: X-Contember-Ref header', async () => {
159
- await executeGraphql(
64
+
65
+ const tester = await createTester(createSchema(TagModel))
66
+
67
+ await tester(
160
68
  gql`
161
69
  mutation {
162
70
  createTag(data: { label: "typescript" }) {
@@ -166,7 +74,7 @@ test('Content API: X-Contember-Ref header', async () => {
166
74
  `,
167
75
  ).expect(200)
168
76
 
169
- const response = await executeGraphql(
77
+ const response = await tester(
170
78
  gql`
171
79
  query {
172
80
  listTag(filter: { label: { eq: "typescript" } }) {
@@ -187,7 +95,7 @@ test('Content API: X-Contember-Ref header', async () => {
187
95
  })
188
96
  .expect(200)
189
97
  const eventKey = response.get('X-Contember-Ref')
190
- await executeGraphql(
98
+ await tester(
191
99
  gql`
192
100
  query {
193
101
  listTag {
@@ -200,7 +108,7 @@ test('Content API: X-Contember-Ref header', async () => {
200
108
  .expect(304)
201
109
 
202
110
  // ignored for mutation
203
- await executeGraphql(
111
+ await tester(
204
112
  gql`
205
113
  mutation {
206
114
  createTag(data: { label: "typescript" }) {
@@ -214,7 +122,9 @@ test('Content API: X-Contember-Ref header', async () => {
214
122
  })
215
123
 
216
124
  test('Content API: invalid schema error', async () => {
217
- await executeGraphql(
125
+ const tester = await createTester(createSchema(TagModel))
126
+
127
+ await tester(
218
128
  gql`
219
129
  mutation {
220
130
  createFoo(data: { label: "graphql" }) {
@@ -227,38 +137,35 @@ test('Content API: invalid schema error', async () => {
227
137
  .expect(response => {
228
138
  assert.deepStrictEqual(
229
139
  response.body.errors[0].message,
230
- 'Cannot query field "createFoo" on type "Mutation". Did you mean "createPost" or "createTag"?',
140
+ 'Cannot query field "createFoo" on type "Mutation". Did you mean "createTag"?',
231
141
  )
232
142
  })
233
143
  })
234
144
 
235
145
  test('Tenant API: sign up, add to a project and check project access', async () => {
146
+ const tester = await createTester(createSchema(TagModel))
147
+
236
148
  const email = `john+${Date.now()}@doe.com`
237
- const signUpResponse = await executeGraphql(
149
+ const identityId = await signUp(email)
150
+ const authKey = await signIn(email)
151
+
152
+ await tester(
238
153
  gql`
239
- mutation($email: String!) {
240
- signUp(email: $email, password: "123456") {
241
- ok
242
- result {
243
- person {
244
- identity {
245
- id
246
- }
247
- }
248
- }
154
+ query {
155
+ listTag {
156
+ id
249
157
  }
250
158
  }
251
159
  `,
252
- {
253
- path: '/tenant',
254
- variables: { email },
255
- },
256
- ).expect(200)
160
+ { authorizationToken: authKey },
161
+ )
162
+ .expect(404)
163
+ .expect({ errors: [{ message: `Project ${tester.projectSlug} NOT found`, code: 404 }] })
164
+
257
165
 
258
- const identityId = signUpResponse.body.data.signUp.result.person.identity.id
166
+ await addProjectMember(identityId, tester.projectSlug)
259
167
 
260
- const authKey = await signIn(email, '123456')
261
- await executeGraphql(
168
+ await tester(
262
169
  gql`
263
170
  query {
264
171
  listTag {
@@ -268,36 +175,63 @@ test('Tenant API: sign up, add to a project and check project access', async ()
268
175
  `,
269
176
  { authorizationToken: authKey },
270
177
  )
271
- .expect(404)
272
- .expect({ errors: [{ message: `Project ${projectSlug} NOT found`, code: 404 }] })
178
+ .expect(response => {
179
+ assert.deepStrictEqual(response.body.data, { listTag: [] })
180
+ })
181
+ .expect(200)
182
+ })
183
+
184
+
185
+ test('Content API: assume membership', async () => {
186
+ const tester = await createTester(createSchema(TagModel, schema => {
187
+ return {
188
+ ...schema,
189
+ acl: {
190
+ roles: {
191
+ test: {
192
+ entities: {},
193
+ variables: {},
194
+ content: {
195
+ assumeMembership: {
196
+ admin: {},
197
+ },
198
+ },
199
+ },
200
+ },
201
+ },
202
+ }
203
+ }))
204
+
205
+ const email = `john+${Date.now()}@doe.com`
206
+ const identityId = await signUp(email)
207
+ const authKey = await signIn(email)
208
+ await addProjectMember(identityId, tester.projectSlug, 'test')
273
209
 
274
- await executeGraphql(
210
+ await tester(
275
211
  gql`
276
- mutation ($identity: String!, $projectSlug: String!) {
277
- addProjectMember(identityId: $identity, projectSlug: $projectSlug, memberships: [{ role: "admin", variables: [] }]) {
278
- ok
212
+ query {
213
+ listTag {
214
+ id
279
215
  }
280
216
  }
281
217
  `,
282
- { path: '/tenant', variables: { identity: identityId, projectSlug } },
218
+ { authorizationToken: authKey },
283
219
  )
284
- .expect(200)
285
- .expect(response => {
286
- assert.deepStrictEqual(response.body.data, { addProjectMember: { ok: true } })
287
- })
220
+ .expect(400)
221
+
288
222
 
289
- await executeGraphql(
223
+
224
+ await tester(
290
225
  gql`
291
226
  query {
292
- listAuthor {
227
+ listTag {
293
228
  id
294
229
  }
295
230
  }
296
231
  `,
297
232
  { authorizationToken: authKey },
298
233
  )
299
- .expect(response => {
300
- assert.deepStrictEqual(response.body.data, { listAuthor: [] })
301
- })
234
+ .set('X-Contember-assume-membership', JSON.stringify({ memberships: [{ role: 'admin', variables: [] }] }))
235
+ .expect({ data: { listTag: [] } })
302
236
  .expect(200)
303
237
  })