@open-xchange/appsuite-codeceptjs 0.1.0

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 (48) hide show
  1. package/.env.defaults +47 -0
  2. package/README.md +40 -0
  3. package/chai.d.ts +5 -0
  4. package/customRerun.js +135 -0
  5. package/global.d.ts +5 -0
  6. package/index.js +187 -0
  7. package/package.json +39 -0
  8. package/src/actor.js +174 -0
  9. package/src/appsuiteHttpClient.js +155 -0
  10. package/src/chai.d.ts +6 -0
  11. package/src/chai.js +58 -0
  12. package/src/contexts/contexts.js +172 -0
  13. package/src/contexts/reseller.js +248 -0
  14. package/src/contexts.js +29 -0
  15. package/src/event.js +54 -0
  16. package/src/helper.js +817 -0
  17. package/src/pageobjects/calendar.js +226 -0
  18. package/src/pageobjects/contacts.js +148 -0
  19. package/src/pageobjects/drive.js +96 -0
  20. package/src/pageobjects/fragments/contact-autocomplete.js +45 -0
  21. package/src/pageobjects/fragments/contact-picker.js +50 -0
  22. package/src/pageobjects/fragments/dialogs.js +41 -0
  23. package/src/pageobjects/fragments/search.js +54 -0
  24. package/src/pageobjects/fragments/settings-mailfilter.js +90 -0
  25. package/src/pageobjects/fragments/settings.js +71 -0
  26. package/src/pageobjects/fragments/tinymce.js +41 -0
  27. package/src/pageobjects/fragments/topbar.js +43 -0
  28. package/src/pageobjects/fragments/viewer.js +67 -0
  29. package/src/pageobjects/mail.js +67 -0
  30. package/src/pageobjects/mobile/mobileCalendar.js +41 -0
  31. package/src/pageobjects/mobile/mobileContacts.js +40 -0
  32. package/src/pageobjects/mobile/mobileMail.js +51 -0
  33. package/src/pageobjects/tasks.js +58 -0
  34. package/src/plugins/emptyModule/index.js +21 -0
  35. package/src/plugins/settingsInit/index.js +35 -0
  36. package/src/plugins/testmetrics/index.js +135 -0
  37. package/src/soap/services/context.js +147 -0
  38. package/src/soap/services/oxaas.js +36 -0
  39. package/src/soap/services/resellerContext.js +65 -0
  40. package/src/soap/services/resellerUser.js +100 -0
  41. package/src/soap/services/user.js +114 -0
  42. package/src/soap/services/util.js +39 -0
  43. package/src/soap/soap.js +172 -0
  44. package/src/users/reseller.js +233 -0
  45. package/src/users/users.js +183 -0
  46. package/src/users.js +29 -0
  47. package/src/util.js +104 -0
  48. package/steps.d.ts +16 -0
@@ -0,0 +1,155 @@
1
+ /**
2
+ * @copyright Copyright (c) Open-Xchange GmbH, Germany <info@open-xchange.com>
3
+ * @license AGPL-3.0
4
+ *
5
+ * This code is free software: you can redistribute it and/or modify
6
+ * it under the terms of the GNU Affero General Public License as published by
7
+ * the Free Software Foundation, either version 3 of the License, or
8
+ * (at your option) any later version.
9
+ *
10
+ * This program is distributed in the hope that it will be useful,
11
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ * GNU Affero General Public License for more details.
14
+ *
15
+ * You should have received a copy of the GNU Affero General Public License
16
+ * along with OX App Suite. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
17
+ *
18
+ * Any use of the work other than as authorized under this license or copyright law is prohibited.
19
+ */
20
+
21
+ const codecept = require('codeceptjs')
22
+ const querystring = require('node:querystring')
23
+
24
+ const pRetry = import('p-retry').then(module => module.default)
25
+
26
+ const cache = {}
27
+ const baseURL = codecept.config.get().helpers.Playwright.url.replace(/\/$/, '')
28
+
29
+ async function fetchWithRetry (url, options) {
30
+ return (await pRetry)(async () => {
31
+ const res = await fetch(url, options)
32
+ let data
33
+ if (res.headers.get('content-type')?.includes('application/json')) {
34
+ data = await res.json()
35
+ } else {
36
+ data = await res.text()
37
+ }
38
+ if (data.error === 'A mail account with the given E-Mail address already exists.') {
39
+ console.log('A mail account with the given E-Mail address already exists.')
40
+ return true
41
+ }
42
+ if (data.error) throw new Error(`HTTP Request Error: ${JSON.stringify(data)}`)
43
+
44
+ return { data, res }
45
+ }, {
46
+ retries: 4,
47
+ onFailedAttempt: error => {
48
+ console.error(`Attempt ${error.attemptNumber} failed. There are ${error.retriesLeft} retries left.`, error)
49
+ }
50
+ })
51
+ }
52
+
53
+ function createHttpClient (options) {
54
+ options = options || {}
55
+ let user = options.user || codecept.container.support('users')[0]
56
+ if (user.toJSON) user = user.toJSON()
57
+
58
+ async function request (url, options = {}) {
59
+ if (user) {
60
+ if (!cache[user.name]) await login(user)
61
+ options.headers = { Cookie: cache[user.name].cookies, ...options.headers }
62
+ if (!options.params) options.params = {}
63
+ options.params.session = cache[user.name].session
64
+ }
65
+ if (options.params) {
66
+ const urlObj = new URL(baseURL + url)
67
+ urlObj.search = new URLSearchParams(options.params).toString()
68
+ url = urlObj.toString()
69
+ } else {
70
+ url = baseURL + url
71
+ }
72
+
73
+ return fetchWithRetry(url, options)
74
+ }
75
+
76
+ return {
77
+ get (url, options = {}) {
78
+ return request(url, { ...options, method: 'GET' })
79
+ },
80
+ post (url, body, options = {}) {
81
+ options = { ...options, method: 'POST', headers: { ...options.headers } }
82
+ if (body instanceof FormData) {
83
+ options.body = body
84
+ } else {
85
+ // Unify content-type header
86
+ if (options.headers['content-type']) {
87
+ options.headers['Content-Type'] = options.headers['content-type']
88
+ delete options.headers['content-type']
89
+ }
90
+ // Set default content-type header
91
+ if (!options.headers['Content-Type']) options.headers['Content-Type'] = 'application/json'
92
+ options.body = JSON.stringify(body)
93
+ }
94
+ return request(url, options)
95
+ },
96
+ put (url, body, options = {}) {
97
+ options = { ...options, method: 'PUT', headers: { 'Content-Type': 'application/json', ...options.headers } }
98
+ if (body) options.body = JSON.stringify(body)
99
+ return request(url, options)
100
+ },
101
+ delete (url, options = {}) {
102
+ return request(url, { ...options, method: 'DELETE' })
103
+ }
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Logs in a user and returns the session data.
109
+ *
110
+ * @param {object} user - The user object containing the name and password.
111
+ * @returns {Promise<object>} - A promise that resolves to the session data.
112
+ * @throws {Error} - If there is an error during the login process.
113
+ */
114
+ async function login (user) {
115
+ if (cache[user.name]) {
116
+ const { session, cookies } = await cache[user.name]
117
+ try {
118
+ if ((await fetchWithRetry(baseURL + '/api/system', {
119
+ headers: { Cookie: cookies },
120
+ params: {
121
+ action: 'ping',
122
+ timestamp: (new Date()).getTime(),
123
+ session
124
+ }
125
+ })).data.data === true) {
126
+ return cache[user.name]
127
+ }
128
+ } catch (e) {
129
+ delete cache[user.name]
130
+ }
131
+ }
132
+
133
+ const { data, res } = await fetchWithRetry(baseURL + '/api/login', {
134
+ method: 'POST',
135
+ headers: {
136
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
137
+ },
138
+ body: querystring.stringify(Object.assign({
139
+ action: 'login',
140
+ client: 'open-xchange-appsuite'
141
+ }, user.login || {
142
+ name: `${user.name}${user.context.id ? '@' + user.context.id : ''}`,
143
+ password: user.password
144
+ }))
145
+ })
146
+ cache[user.name] = {
147
+ ...data,
148
+ cookies: res.headers.getSetCookie().map(item => item.split(';')[0]).join(';')
149
+ }
150
+ }
151
+
152
+ module.exports = {
153
+ createHttpClient,
154
+ login
155
+ }
package/src/chai.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ declare module Chai {
2
+ interface Assertion {
3
+ accessible: void;
4
+ }
5
+ }
6
+ export {}
package/src/chai.js ADDED
@@ -0,0 +1,58 @@
1
+ /**
2
+ * @copyright Copyright (c) Open-Xchange GmbH, Germany <info@open-xchange.com>
3
+ * @license AGPL-3.0
4
+ *
5
+ * This code is free software: you can redistribute it and/or modify
6
+ * it under the terms of the GNU Affero General Public License as published by
7
+ * the Free Software Foundation, either version 3 of the License, or
8
+ * (at your option) any later version.
9
+ *
10
+ * This program is distributed in the hope that it will be useful,
11
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ * GNU Affero General Public License for more details.
14
+ *
15
+ * You should have received a copy of the GNU Affero General Public License
16
+ * along with OX App Suite. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
17
+ *
18
+ * Any use of the work other than as authorized under this license or copyright law is prohibited.
19
+ */
20
+
21
+ import('chai').then(chai => {
22
+ const chaiSubset = require('chai-subset')
23
+ chai.use(chaiSubset)
24
+
25
+ chai.util.addProperty(chai.Assertion.prototype, 'accessible', function () {
26
+ const problems = ['\n', 'Accessibility Violations (' + this._obj.violations.length + ')', '---']
27
+ const pad = '\n '
28
+ if (this._obj.violations.length) {
29
+ for (const violation of this._obj.violations) {
30
+ problems.push(pad + '[' + violation.impact.toUpperCase() + '] ' + violation.help + ' (ID: ' + violation.id + ')\n')
31
+ for (const node of violation.nodes) {
32
+ problems.push(node.failureSummary.split('\n').join(pad))
33
+ problems.push(' ' + node.target + ' => ' + node.html)
34
+ const relatedNodes = []
35
+ for (const combinedNodes of [node.all, node.any, node.none]) {
36
+ if (combinedNodes.length > 0) {
37
+ for (const any of combinedNodes) {
38
+ for (const relatedNode of any.relatedNodes) {
39
+ relatedNodes.push(' ' + relatedNode.target + ' => ' + relatedNode.html)
40
+ }
41
+ }
42
+ }
43
+ }
44
+ if (relatedNodes.length > 0) problems.push(relatedNodes.join(pad))
45
+ }
46
+ problems.push(pad + '---\n')
47
+ }
48
+ }
49
+ this.assert(
50
+ this._obj.violations.length === 0,
51
+ `expected to have no violations:\n ${problems.join(pad)}`,
52
+ 'expected to have violations'
53
+ )
54
+ })
55
+
56
+ globalThis.expect = chai.expect
57
+ globalThis.assert = chai.assert
58
+ })
@@ -0,0 +1,172 @@
1
+ /**
2
+ * @copyright Copyright (c) Open-Xchange GmbH, Germany <info@open-xchange.com>
3
+ * @license AGPL-3.0
4
+ *
5
+ * This code is free software: you can redistribute it and/or modify
6
+ * it under the terms of the GNU Affero General Public License as published by
7
+ * the Free Software Foundation, either version 3 of the License, or
8
+ * (at your option) any later version.
9
+ *
10
+ * This program is distributed in the hope that it will be useful,
11
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ * GNU Affero General Public License for more details.
14
+ *
15
+ * You should have received a copy of the GNU Affero General Public License
16
+ * along with OX App Suite. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
17
+ *
18
+ * Any use of the work other than as authorized under this license or copyright law is prohibited.
19
+ */
20
+
21
+ const created = []
22
+ const users = require('../users/users')()
23
+ const util = require('../util')
24
+ const event = require('../event')
25
+ const contextService = require('../soap/services/context')
26
+ const utilService = require('../soap/services/util')
27
+
28
+ class Context {
29
+ constructor ({ ctxdata, admin, auth }) {
30
+ this.id = ctxdata.id
31
+ this.ctxdata = ctxdata
32
+ admin.login = admin.login || admin.name
33
+ this.admin = admin
34
+ this.auth = auth
35
+ return new Proxy(this, {
36
+ get (target, prop) {
37
+ return prop in target ? target[prop] : target.ctxdata[prop]
38
+ }
39
+ })
40
+ }
41
+
42
+ async remove () {
43
+ const defaultContext = await contextService.getDefault()
44
+ // do not remove default context
45
+ if (defaultContext !== undefined && this.ctxdata.id === defaultContext.id) throw new Error('Cannot remove default context')
46
+ try {
47
+ await contextService.remove(this.ctxdata.id)
48
+ } catch (e) {
49
+ if (!/Context \d+ does not exist/.test(e.message)) throw new util.PropagatedError(e)
50
+ else console.error(e.message)
51
+ }
52
+ created.splice(0, created.length, ...created.filter(c => c !== this))
53
+ users.splice(0, users.length, ...users.filter(u => u.context.id !== this.id))
54
+ event.emit(event.provisioning.context.removed, this)
55
+ }
56
+
57
+ hasConfig (key, value) {
58
+ // Config structure is the following { userAttributes: { entries: [{ key: 'config', value: { entries: [{ key: 'key', value: value }] }}]} }
59
+ // Note that the soap lib will replace arrays with a single element by that element
60
+
61
+ // check if config exists and create config path if it does not exist
62
+ const emptyConfig = { entries: [{ key: 'config', value: null }] }
63
+ this.ctxdata.userAttributes = this.ctxdata.userAttributes || emptyConfig
64
+ const userAttributes = this.ctxdata.userAttributes
65
+ const entries = userAttributes.entries = userAttributes.entries ? [].concat(userAttributes.entries) : []
66
+ let configEntry = entries.find(e => e.key === 'config')
67
+ if (!configEntry) {
68
+ entries.push({ key: 'config', value: {} })
69
+ configEntry = entries[entries.length - 1]
70
+ }
71
+ const config = configEntry.value = configEntry.value || { entries: [] }
72
+ const configEntries = config.entries = config.entries ? [].concat(config.entries) : []
73
+ let targetConfig = configEntries.find(e => e.key === key)
74
+ if (!targetConfig) {
75
+ configEntries.push({ key })
76
+ targetConfig = configEntries[configEntries.length - 1]
77
+ }
78
+ targetConfig.value = value
79
+
80
+ return contextService.change({ id: this.ctxdata.id, userAttributes })
81
+ }
82
+
83
+ hasCapability (capsToAdd) {
84
+ return contextService.changeCapabilities(this.id, capsToAdd, undefined)
85
+ }
86
+
87
+ doesntHaveCapability (capsToRemove) {
88
+ return contextService.changeCapabilities(this.id, undefined, capsToRemove)
89
+ }
90
+
91
+ hasAccessCombination (accessCombinationName) {
92
+ return contextService.changeModuleAccessByName(this.id, accessCombinationName)
93
+ }
94
+
95
+ getModuleAccess () {
96
+ return contextService.getModuleAccess(this.id)
97
+ }
98
+
99
+ hasModuleAccess (moduleAccess) {
100
+ return contextService.changeModuleAccess(this.id, moduleAccess)
101
+ }
102
+
103
+ hasQuota (maxQuota) {
104
+ this.ctxdata.maxQuota = maxQuota
105
+ return contextService.change({ id: this.id, maxQuota })
106
+ }
107
+
108
+ static async create (ctx = { }, adminUser = { }, auth = util.admin()) {
109
+ adminUser = Object.assign({
110
+ name: 'oxadmin',
111
+ password: 'secret',
112
+ display_name: 'context admin',
113
+ sur_name: 'admin',
114
+ given_name: 'context',
115
+ email1: `${adminUser.name || 'oxadmin'}@${util.mxDomain()}`,
116
+ primaryEmail: `${adminUser.name || 'oxadmin'}@${util.mxDomain()}`
117
+ }, adminUser)
118
+
119
+ let filestoreId = ctx.filestoreId
120
+
121
+ if (typeof filestoreId === 'undefined') {
122
+ filestoreId = await utilService.getFilestorageId()
123
+ }
124
+ const newCtx = Object.assign({ id: Number(util.userContextId()), maxQuota: -1, filestoreId }, ctx)
125
+ event.emit(event.provisioning.context.create, newCtx, adminUser, auth)
126
+ let data
127
+
128
+ try {
129
+ data = await contextService.create(newCtx)
130
+ } catch (e) {
131
+ newCtx.id = util.addJitter(newCtx.id)
132
+ return this.create(newCtx)
133
+ }
134
+
135
+ const context = new Context({ ctxdata: data, admin: adminUser, auth })
136
+ created.push(context)
137
+ // only provide defaults for fresh contexts
138
+ await context.hasAccessCombination('all')
139
+ event.emit(event.provisioning.context.created, context)
140
+ return context
141
+ }
142
+
143
+ static async reuse (ctx, admin = { login: 'oxadmin', password: 'secret' }, auth = util.admin()) {
144
+ const searchCtx = Object.assign({ id: util.userContextId() }, ctx)
145
+ const existingContext = created.find(c => c.id === searchCtx.id)
146
+ if (existingContext) return existingContext
147
+
148
+ const data = await contextService.get(searchCtx)
149
+
150
+ const context = new Context({ ctxdata: data, admin, auth })
151
+ return context
152
+ }
153
+
154
+ static async removeAll (auth) {
155
+ let ctxt = created.pop()
156
+ while (ctxt) {
157
+ if (auth) ctxt.auth = auth
158
+ await ctxt.remove()
159
+ ctxt = created.pop()
160
+ }
161
+ return created
162
+ }
163
+ }
164
+
165
+ module.exports = () => {
166
+ return new Proxy(created, {
167
+ get: function (target, prop) {
168
+ if (prop in target) return target[prop]
169
+ return Context[prop]
170
+ }
171
+ })
172
+ }
@@ -0,0 +1,248 @@
1
+ /**
2
+ * @copyright Copyright (c) Open-Xchange GmbH, Germany <info@open-xchange.com>
3
+ * @license AGPL-3.0
4
+ *
5
+ * This code is free software: you can redistribute it and/or modify
6
+ * it under the terms of the GNU Affero General Public License as published by
7
+ * the Free Software Foundation, either version 3 of the License, or
8
+ * (at your option) any later version.
9
+ *
10
+ * This program is distributed in the hope that it will be useful,
11
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ * GNU Affero General Public License for more details.
14
+ *
15
+ * You should have received a copy of the GNU Affero General Public License
16
+ * along with OX App Suite. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
17
+ *
18
+ * Any use of the work other than as authorized under this license or copyright law is prohibited.
19
+ */
20
+
21
+ const event = require('../event')
22
+ const users = require('../users/reseller')()
23
+ const util = require('../util')
24
+ const resellerContextService = require('../soap/services/resellerContext')
25
+ const resellerUserService = require('../soap/services/resellerUser')
26
+ const contexts = []
27
+ const created = []
28
+
29
+ class ResellerContext {
30
+ constructor (ctx, admin, auth) {
31
+ this.ctxdata = ctx
32
+ this.admin = admin
33
+ this.auth = auth
34
+ this.id = ctx.id
35
+ this.userAttributes = ctx.userAttributes
36
+
37
+ return new Proxy(this, {
38
+ get (target, prop) {
39
+ return prop in target ? target[prop] : target.ctxdata[prop]
40
+ }
41
+ })
42
+ }
43
+
44
+ static async removeAll (auth) {
45
+ let ctx = created.pop()
46
+ while (ctx) {
47
+ if (auth) ctx.auth = auth
48
+ await ctx.remove()
49
+ ctx = created.pop()
50
+ }
51
+ return ResellerContext
52
+ }
53
+
54
+ async remove () {
55
+ await resellerContextService.remove(this.id)
56
+ contexts.splice(0, contexts.length, ...contexts.filter(c => c !== this))
57
+ created.splice(0, created.length, ...created.filter(c => c !== this))
58
+ users.splice(0, users.length, ...users.filter(u => u.context.id !== this.id))
59
+ event.emit(event.provisioning.context.removed, this)
60
+ }
61
+
62
+ hasCapability (capability) {
63
+ return this._setCapability(capability, 'true')
64
+ }
65
+
66
+ _setCapability (cap, value) {
67
+ const userAttributes = this.userAttributes || { entries: [] }
68
+ const configMap = userAttributes.entries.find(entry => entry.key === 'config') || { key: 'config' }
69
+ configMap.value = configMap.value || { entries: [] }
70
+ configMap.value.entries.push({ key: `com.openexchange.capability.${cap}`, value })
71
+ return resellerContextService.change({
72
+ ctx: {
73
+ id: this.id,
74
+ userAttributes
75
+ }
76
+ }).then(() => {
77
+ return resellerContextService.get({
78
+ ctx: { id: this.id },
79
+ auth: { login: this.admin.name, password: this.admin.password }
80
+ })
81
+ }).then(r => {
82
+ this.ctxdata = r[0].return
83
+ return this
84
+ }).catch((err) => { throw new util.PropagatedError(err) })
85
+ }
86
+
87
+ doesntHaveCapability (capability) {
88
+ return this._setCapability(capability, false)
89
+ }
90
+
91
+ hasConfig (key, value) {
92
+ // Config structure is the following { userAttributes: { entries: [{ key: 'config', value: { entries: [{ key: 'key', value: value }] }}]} }
93
+ // Note that the soap lib will replace arrays with a single element by that element
94
+
95
+ // check if config exists and create config path if it does not exist
96
+ const emptyConfig = { entries: [{ key: 'config', value: null }] }
97
+ this.ctxdata.userAttributes = this.ctxdata.userAttributes || emptyConfig
98
+ const userAttributes = this.ctxdata.userAttributes
99
+ const entries = userAttributes.entries = userAttributes.entries ? [].concat(userAttributes.entries) : []
100
+ let configEntry = entries.find(e => e.key === 'config')
101
+ if (!configEntry) {
102
+ entries.push({ key: 'config', value: {} })
103
+ configEntry = entries[entries.length - 1]
104
+ }
105
+ const config = configEntry.value = configEntry.value || { entries: [] }
106
+ const configEntries = config.entries = config.entries ? [].concat(config.entries) : []
107
+ let targetConfig = configEntries.find(e => e.key === key)
108
+ if (!targetConfig) {
109
+ configEntries.push({ key })
110
+ targetConfig = configEntries[configEntries.length - 1]
111
+ }
112
+ targetConfig.value = value
113
+
114
+ return resellerContextService.change({ id: this.ctxdata.id, userAttributes })
115
+ }
116
+
117
+ hasTaxonomy (taxonomy) {
118
+ // check if config exists and create config path if it does not exist
119
+ const userAttributes = this.ctxdata.userAttributes || {}
120
+ const entries = userAttributes.entries = userAttributes.entries ? [].concat(userAttributes.entries) : []
121
+ let taxonomyEntry = entries.find(e => e.key === 'taxonomy')
122
+ if (!taxonomyEntry) {
123
+ entries.push({ key: 'taxonomy', value: { entries: [{ key: 'types' }] } })
124
+ taxonomyEntry = entries[entries.length - 1]
125
+ }
126
+ const targetEntry = taxonomyEntry.value.entries.find(e => e.key === 'types')
127
+ targetEntry.value = taxonomy
128
+
129
+ return resellerContextService.change({ id: this.ctxdata.id, userAttributes })
130
+ }
131
+
132
+ hasQuota (maxQuota) {
133
+ this.ctxdata.maxQuota = maxQuota
134
+ return resellerContextService.change({ id: this.id, maxQuota })
135
+ }
136
+
137
+ getModuleAccess () {
138
+ return resellerContextService.getModuleAccess(this.id)
139
+ }
140
+
141
+ async hasModuleAccess (moduleAccess) {
142
+ return resellerContextService.changeModuleAccess(this.id, moduleAccess)
143
+ }
144
+
145
+ get users () {
146
+ return resellerUserService.listAll({
147
+ ctx: { id: this.ctxdata.id }
148
+ }).then(([{ return: list }]) => {
149
+ return Promise.all(
150
+ list.map(u =>
151
+ resellerUserService.get({
152
+ user: { id: u.id },
153
+ ctx: { id: this.ctxdata.id },
154
+ auth: this.auth
155
+ }).then(([{ return: user }]) => user, (err) => { throw new util.PropagatedError(err) })
156
+ )
157
+ )
158
+ }, (err) => { throw new util.PropagatedError(err) })
159
+ }
160
+
161
+ static defaultAdmin () {
162
+ return {
163
+ password: 'secret',
164
+ display_name: 'context admin',
165
+ sur_name: 'admin',
166
+ given_name: 'context',
167
+ email1: `oxadmin@${util.mxDomain()}`,
168
+ primaryEmail: `oxadmin@${util.mxDomain()}`
169
+ }
170
+ }
171
+
172
+ static async create (ctx = { }, adminUser = { }, auth = util.admin(), numberOfUsers = Number(process.env.PROVISIONING_USERS || 10)) {
173
+ ctx = Object.assign({ id: util.userContextId(), maxQuota: -1 }, ctx)
174
+ adminUser = Object.assign({
175
+ name: `${ctx.name || ctx.id}_admin`
176
+ }, this.defaultAdmin(), adminUser)
177
+ let data; let neededPrefix = ctx._auto_prefix
178
+ delete ctx._auto_prefix
179
+ ctx.name = (process.env.CONTEXT_PREFIX || '') + (ctx.name || `${ctx.id}`)
180
+ event.emit(event.provisioning.context.create, ctx, adminUser, auth)
181
+ try {
182
+ data = await resellerContextService.create({
183
+ ctx,
184
+ admin_user: adminUser,
185
+ auth
186
+ })
187
+ } catch (error) {
188
+ if (!neededPrefix && error.message.match(/name must beginn with ([^ ;]+)/)) {
189
+ const oldName = ctx.name
190
+ delete ctx.name
191
+ neededPrefix = error.message.match(/name must beginn with ([^ ;]+)/)[1]
192
+ return ResellerContext.create(Object.assign({
193
+ _auto_prefix: true,
194
+ name: `${neededPrefix}${oldName}`
195
+ }, ctx), adminUser, auth)
196
+ }
197
+ if (error.message.match(/already exists/)) {
198
+ const id = String(util.addJitter(Number.parseInt(ctx.id, 10)))
199
+ return ResellerContext.create(Object.assign({}, ctx, { name: ctx.name.replace(/_\d+/, `_${id}`), id }))
200
+ }
201
+ throw new util.PropagatedError(error)
202
+ }
203
+ const index = created.push(new ResellerContext(data, adminUser, auth)) - 1
204
+ event.emit(event.provisioning.context.created, created[index])
205
+
206
+ for (let i = 0; i < numberOfUsers; i++) {
207
+ // @ts-ignore
208
+ await users.create(users.getRandom(), created[index], true)
209
+ }
210
+
211
+ return created[index]
212
+ }
213
+
214
+ static async reuse (ctx, adminUser = ResellerContext.defaultAdmin(), auth = util.admin()) {
215
+ const existingContext = created.find(c => (c.name === ctx.name) || (c.id === ctx.id) || (new RegExp(`_${ctx.id}$`).test(c.name)))
216
+ if (existingContext) return existingContext
217
+
218
+ await ResellerContext.fetchContexts()
219
+ const remoteCtx = contexts.find(c => (c.name === ctx.name) || (c.id === ctx.id) || (new RegExp(`_${ctx.id}$`).test(c.name)))
220
+ if (!remoteCtx.auth) remoteCtx.auth = auth
221
+ if (!remoteCtx.admin_user) remoteCtx.admin_user = adminUser
222
+ return remoteCtx
223
+ }
224
+
225
+ static async all () {
226
+ if (contexts.length === 0) await this.fetchContexts()
227
+ return created.concat(contexts)
228
+ }
229
+
230
+ static async fetchContexts () {
231
+ const list = await resellerContextService.listAll()
232
+ .catch((err) => { throw new util.PropagatedError(err) })
233
+ contexts.splice(0, contexts.length)
234
+ contexts.push.apply(contexts, list.map(c => new ResellerContext(c)))
235
+ }
236
+ }
237
+
238
+ // can't return the class directly, because codecept tries to execute functions
239
+ module.exports = function () {
240
+ ResellerContext.fetchContexts()
241
+ return new Proxy(created, {
242
+ // act like an array if an index is being used
243
+ get (target, prop) {
244
+ if (prop in target) return target[prop]
245
+ return ResellerContext[prop]
246
+ }
247
+ })
248
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @copyright Copyright (c) Open-Xchange GmbH, Germany <info@open-xchange.com>
3
+ * @license AGPL-3.0
4
+ *
5
+ * This code is free software: you can redistribute it and/or modify
6
+ * it under the terms of the GNU Affero General Public License as published by
7
+ * the Free Software Foundation, either version 3 of the License, or
8
+ * (at your option) any later version.
9
+ *
10
+ * This program is distributed in the hope that it will be useful,
11
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ * GNU Affero General Public License for more details.
14
+ *
15
+ * You should have received a copy of the GNU Affero General Public License
16
+ * along with OX App Suite. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>.
17
+ *
18
+ * Any use of the work other than as authorized under this license or copyright law is prohibited.
19
+ */
20
+
21
+ let moduleToLoad
22
+
23
+ if (process.env.PROVISIONING_API === 'reseller') {
24
+ moduleToLoad = './contexts/reseller'
25
+ } else {
26
+ moduleToLoad = './contexts/contexts'
27
+ }
28
+
29
+ module.exports = require(moduleToLoad)