@open-xchange/appsuite-codeceptjs 0.6.2 → 0.6.4
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.
- package/README.md +70 -1
- package/container/Dockerfile +1 -1
- package/eslint.config.mjs +1 -1
- package/global.d.ts +3 -6
- package/package.json +18 -14
- package/pnpm-workspace.yaml +4 -0
- package/spec/pageobjects/calendar.test.js +123 -0
- package/src/actor.js +1 -1
- package/src/chai.js +0 -3
- package/src/contexts/contexts.js +1 -1
- package/src/contexts/reseller.js +1 -2
- package/src/pageobjects/calendar.js +19 -16
- package/src/pageobjects/contacts.js +2 -0
- package/src/pageobjects/mail.js +0 -1
- package/src/pageobjects/mobile/mobileContacts.js +1 -2
- package/src/{soap/services/oxaas.js → pageobjects/util.js} +11 -13
- package/src/users/reseller.js +1 -2
- package/src/users/users.js +1 -1
- package/src/util.js +1 -1
- package/chai.d.ts +0 -5
- package/src/chai.d.ts +0 -6
- package/src/soap/services/context.js +0 -170
- package/src/soap/services/resellerContext.js +0 -65
- package/src/soap/services/resellerUser.js +0 -100
- package/src/soap/services/user.js +0 -114
- package/src/soap/services/util.js +0 -39
- package/src/soap/soap.js +0 -176
package/README.md
CHANGED
|
@@ -1,3 +1,72 @@
|
|
|
1
1
|
# Open-Xchange App Suite: CodeceptJS
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
App Suite specific CodeceptJS tooling.
|
|
4
|
+
|
|
5
|
+
## Custom Helpers
|
|
6
|
+
|
|
7
|
+
The file `src/helpers.js` contains App Suite specific CodeceptJS helpers. It is possible to overwrite any of these helpers or add new ones in projects that use this package. This might be useful for maintenance work on existing helpers or when developing new ones and can be achieved with the following changes:
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
// CodeceptJS configuration of a local package
|
|
11
|
+
// file: e2e/codecept.conf.js
|
|
12
|
+
|
|
13
|
+
const { config } = require('@open-xchange/appsuite-codeceptjs')
|
|
14
|
+
|
|
15
|
+
// import local helpers
|
|
16
|
+
config.helpers.AppSuite = {
|
|
17
|
+
require: './helper'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports.config = config
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
// Local helpers
|
|
25
|
+
// file: e2e/helper.js
|
|
26
|
+
|
|
27
|
+
const Helper = require('@open-xchange/appsuite-codeceptjs/src/helper')
|
|
28
|
+
|
|
29
|
+
class CustomHelper extends Helper {
|
|
30
|
+
// This overwrites the existing `selectFolder` helper
|
|
31
|
+
async selectFolder (locator) {
|
|
32
|
+
locator = '.folder-tree ' + locator
|
|
33
|
+
await this.helpers.Playwright.page.locator(locator).click()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// This creates the new helper `newCostumHelper`
|
|
37
|
+
async newCostumHelper () {
|
|
38
|
+
await this.helpers.Playwright.waitForVisible({ css: 'html.complete' }, 10)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = CustomHelper
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Local Configuration Overwrite
|
|
46
|
+
|
|
47
|
+
You can use the config object for local customization of the default CodeceptJS configuration provided by this package. For example you can change the tests directory the following way:
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
// CodeceptJS configuration of a local package
|
|
51
|
+
// file: e2e/codecept.conf.js
|
|
52
|
+
|
|
53
|
+
const { config } = require('@open-xchange/appsuite-codeceptjs')
|
|
54
|
+
|
|
55
|
+
config.tests = './costum_directory/*_test.js'
|
|
56
|
+
|
|
57
|
+
module.exports.config = config
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Known Issues
|
|
61
|
+
|
|
62
|
+
Add this to your `package.json` to ignore CVE-2025-5889 when installing this package with `pnpm`:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
"pnpm": {
|
|
66
|
+
"auditConfig": {
|
|
67
|
+
"ignoreCves": [
|
|
68
|
+
"CVE-2025-5889"
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
package/container/Dockerfile
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
FROM registry.gitlab.
|
|
1
|
+
FROM registry.gitlab.com/openxchange/appsuite/web-foundation/base-images/playwright:v1.53.0-jammy
|
package/eslint.config.mjs
CHANGED
|
@@ -48,7 +48,7 @@ export default [
|
|
|
48
48
|
languageOptions: {
|
|
49
49
|
globals: {
|
|
50
50
|
...codeceptjs[0].languageOptions.globals,
|
|
51
|
-
...mocha.configs.
|
|
51
|
+
...mocha.configs.recommended.languageOptions.globals,
|
|
52
52
|
Feature: true,
|
|
53
53
|
Scenario: true,
|
|
54
54
|
Before: true,
|
package/global.d.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/* eslint-disable
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
var assert: Chai.AssertStatic
|
|
5
|
-
const codecept_dir: string
|
|
6
|
-
const output_dir: string
|
|
1
|
+
/* eslint-disable camelcase */
|
|
2
|
+
declare const codecept_dir: string
|
|
3
|
+
declare const output_dir: string
|
package/package.json
CHANGED
|
@@ -1,44 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-xchange/appsuite-codeceptjs",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4",
|
|
4
4
|
"description": "OX App Suite CodeceptJS Configuration and Helpers",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"e2e-rerun": "./customRerun.js"
|
|
8
8
|
},
|
|
9
|
-
"repository":
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://gitlab.com/openxchange/appsuite/web-foundation/tools",
|
|
12
|
+
"directory": "packages/appsuite-codeceptjs"
|
|
13
|
+
},
|
|
10
14
|
"license": "AGPL-3.0-or-later",
|
|
11
15
|
"private": false,
|
|
12
16
|
"dependencies": {
|
|
13
17
|
"@axe-core/playwright": "^4.10.1",
|
|
14
18
|
"@codeceptjs/helper": "^2.0.4",
|
|
15
19
|
"@influxdata/influxdb-client": "^1.35.0",
|
|
16
|
-
"@
|
|
17
|
-
"@playwright/test": "1.49.1",
|
|
20
|
+
"@playwright/test": "1.53.0",
|
|
18
21
|
"allure-codeceptjs": "2.15.1",
|
|
19
|
-
"chai": "^5.
|
|
20
|
-
"chai-subset": "^1.6.0",
|
|
22
|
+
"chai": "^5.2.0",
|
|
21
23
|
"chalk": "^4.1.2",
|
|
22
24
|
"chalk-table": "^1.0.2",
|
|
23
|
-
"codeceptjs": "3.
|
|
25
|
+
"codeceptjs": "3.7.3",
|
|
24
26
|
"dotenv": "^16.4.7",
|
|
25
|
-
"mocha": "^11.0
|
|
27
|
+
"mocha": "^11.1.0",
|
|
26
28
|
"mocha-junit-reporter": "^2.2.1",
|
|
27
29
|
"mocha-multi": "^1.1.7",
|
|
28
30
|
"moment": "^2.30.1",
|
|
29
|
-
"moment-timezone": "^0.
|
|
31
|
+
"moment-timezone": "^0.6.0",
|
|
30
32
|
"p-retry": "^6.2.1",
|
|
31
|
-
"playwright-core": "1.
|
|
33
|
+
"playwright-core": "1.53.0",
|
|
32
34
|
"short-uuid": "^5.2.0",
|
|
33
|
-
"soap": "
|
|
35
|
+
"@open-xchange/soap-client": "0.0.1",
|
|
36
|
+
"@open-xchange/codecept-horizontal-scaler": "0.1.12"
|
|
34
37
|
},
|
|
35
38
|
"devDependencies": {
|
|
36
|
-
"@types/node": "^
|
|
39
|
+
"@types/node": "^24.0.0",
|
|
37
40
|
"ts-node": "^10.9.2",
|
|
38
|
-
"typescript": "^5.
|
|
41
|
+
"typescript": "^5.8.2",
|
|
39
42
|
"@open-xchange/lint": "0.2.0"
|
|
40
43
|
},
|
|
41
44
|
"scripts": {
|
|
42
|
-
"lint": "eslint ."
|
|
45
|
+
"lint": "eslint .",
|
|
46
|
+
"test": "mocha --color 'spec/**/*.test.js'"
|
|
43
47
|
}
|
|
44
48
|
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
const { expect } = require('chai')
|
|
2
|
+
const { getMonday } = require('../../src/pageobjects/util')
|
|
3
|
+
const moment = require('moment')
|
|
4
|
+
|
|
5
|
+
const FORMAT = 'YYYY-MM-DD'
|
|
6
|
+
|
|
7
|
+
describe('Calendar', function () {
|
|
8
|
+
describe('getMonday()', function () {
|
|
9
|
+
describe('Week starts on Monday (locale=de)', function () {
|
|
10
|
+
it('should see the week starting on Monday (1)', function () {
|
|
11
|
+
expect(moment.localeData('de').firstDayOfWeek()).to.equal(1)
|
|
12
|
+
})
|
|
13
|
+
it('should return 2025-06-02 on 2025-06-06 (last Friday)', function () {
|
|
14
|
+
const date = moment('2025-06-06').locale('de')
|
|
15
|
+
expect(getMonday(date).format(FORMAT)).to.equal('2025-06-02')
|
|
16
|
+
})
|
|
17
|
+
it('should return 2025-06-02 on 2025-06-07 (Saturday)', function () {
|
|
18
|
+
const date = moment('2025-06-07').locale('de')
|
|
19
|
+
expect(getMonday(date).format(FORMAT)).to.equal('2025-06-02')
|
|
20
|
+
})
|
|
21
|
+
it('should return 2025-06-02 on 2025-06-08 (Sunday)', function () {
|
|
22
|
+
const date = moment('2025-06-08').locale('de')
|
|
23
|
+
expect(getMonday(date).format(FORMAT)).to.equal('2025-06-02')
|
|
24
|
+
})
|
|
25
|
+
it('should return 2025-06-09 on 2025-06-09 (Monday)', function () {
|
|
26
|
+
const date = moment('2025-06-09').locale('de')
|
|
27
|
+
expect(getMonday(date).format(FORMAT)).to.equal('2025-06-09')
|
|
28
|
+
})
|
|
29
|
+
it('should return 2025-06-13 on 2025-06-09 (Friday)', function () {
|
|
30
|
+
const date = moment('2025-06-13').locale('de')
|
|
31
|
+
expect(getMonday(date).format(FORMAT)).to.equal('2025-06-09')
|
|
32
|
+
})
|
|
33
|
+
it('should return 2025-06-02 on 2025-06-14 (next Saturday)', function () {
|
|
34
|
+
const date = moment('2025-06-14').locale('de')
|
|
35
|
+
expect(getMonday(date).format(FORMAT)).to.equal('2025-06-09')
|
|
36
|
+
})
|
|
37
|
+
it('should return 2025-06-02 on 2025-06-15 (next Sunday)', function () {
|
|
38
|
+
const date = moment('2025-06-15').locale('de')
|
|
39
|
+
expect(getMonday(date).format(FORMAT)).to.equal('2025-06-09')
|
|
40
|
+
})
|
|
41
|
+
it('should return 2025-06-02 on 2025-06-16 (next Monday)', function () {
|
|
42
|
+
const date = moment('2025-06-16').locale('de')
|
|
43
|
+
expect(getMonday(date).format(FORMAT)).to.equal('2025-06-16')
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
describe('Week starts on Sunday (locale=en)', function () {
|
|
48
|
+
it('should see the week starting on Sunday (0)', function () {
|
|
49
|
+
expect(moment.localeData('en').firstDayOfWeek()).to.equal(0)
|
|
50
|
+
})
|
|
51
|
+
it('should return 2025-06-02 on 2025-06-06 (last Friday)', function () {
|
|
52
|
+
const date = moment('2025-06-06').locale('en')
|
|
53
|
+
expect(getMonday(date).format(FORMAT)).to.equal('2025-06-02')
|
|
54
|
+
})
|
|
55
|
+
it('should return 2025-06-02 on 2025-06-07 (Saturday)', function () {
|
|
56
|
+
const date = moment('2025-06-07').locale('en')
|
|
57
|
+
expect(getMonday(date).format(FORMAT)).to.equal('2025-06-02')
|
|
58
|
+
})
|
|
59
|
+
it('should return 2025-06-09 on 2025-06-08 (Sunday)', function () {
|
|
60
|
+
const date = moment('2025-06-08').locale('en')
|
|
61
|
+
expect(getMonday(date).format(FORMAT)).to.equal('2025-06-09')
|
|
62
|
+
})
|
|
63
|
+
it('should return 2025-06-09 on 2025-06-09 (Monday)', function () {
|
|
64
|
+
const date = moment('2025-06-09').locale('en')
|
|
65
|
+
expect(getMonday(date).format(FORMAT)).to.equal('2025-06-09')
|
|
66
|
+
})
|
|
67
|
+
it('should return 2025-06-13 on 2025-06-09 (Friday)', function () {
|
|
68
|
+
const date = moment('2025-06-13').locale('en')
|
|
69
|
+
expect(getMonday(date).format(FORMAT)).to.equal('2025-06-09')
|
|
70
|
+
})
|
|
71
|
+
it('should return 2025-06-02 on 2025-06-14 (next Saturday)', function () {
|
|
72
|
+
const date = moment('2025-06-14').locale('en')
|
|
73
|
+
expect(getMonday(date).format(FORMAT)).to.equal('2025-06-09')
|
|
74
|
+
})
|
|
75
|
+
it('should return 2025-06-02 on 2025-06-15 (next Sunday)', function () {
|
|
76
|
+
const date = moment('2025-06-15').locale('en')
|
|
77
|
+
expect(getMonday(date).format(FORMAT)).to.equal('2025-06-16')
|
|
78
|
+
})
|
|
79
|
+
it('should return 2025-06-02 on 2025-06-16 (next Monday)', function () {
|
|
80
|
+
const date = moment('2025-06-16').locale('en')
|
|
81
|
+
expect(getMonday(date).format(FORMAT)).to.equal('2025-06-16')
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
describe('Week starts on Saturday (locale=tzm-latn)', function () {
|
|
86
|
+
it('should see the week starting on Saturday (6)', function () {
|
|
87
|
+
expect(moment.localeData('tzm-latn').firstDayOfWeek()).to.equal(6)
|
|
88
|
+
})
|
|
89
|
+
it('should return 2025-06-02 on 2025-06-06 (last Friday)', function () {
|
|
90
|
+
const date = moment('2025-06-06').locale('tzm-latn')
|
|
91
|
+
expect(getMonday(date).format(FORMAT)).to.equal('2025-06-02')
|
|
92
|
+
})
|
|
93
|
+
it('should return 2025-06-09 on 2025-06-07 (Saturday)', function () {
|
|
94
|
+
const date = moment('2025-06-07').locale('tzm-latn')
|
|
95
|
+
expect(getMonday(date).format(FORMAT)).to.equal('2025-06-09')
|
|
96
|
+
})
|
|
97
|
+
it('should return 2025-06-09 on 2025-06-08 (Sunday)', function () {
|
|
98
|
+
const date = moment('2025-06-08').locale('tzm-latn')
|
|
99
|
+
expect(getMonday(date).format(FORMAT)).to.equal('2025-06-09')
|
|
100
|
+
})
|
|
101
|
+
it('should return 2025-06-09 on 2025-06-09 (Monday)', function () {
|
|
102
|
+
const date = moment('2025-06-09').locale('tzm-latn')
|
|
103
|
+
expect(getMonday(date).format(FORMAT)).to.equal('2025-06-09')
|
|
104
|
+
})
|
|
105
|
+
it('should return 2025-06-13 on 2025-06-09 (Friday)', function () {
|
|
106
|
+
const date = moment('2025-06-13').locale('tzm-latn')
|
|
107
|
+
expect(getMonday(date).format(FORMAT)).to.equal('2025-06-09')
|
|
108
|
+
})
|
|
109
|
+
it('should return 2025-06-02 on 2025-06-14 (next Saturday)', function () {
|
|
110
|
+
const date = moment('2025-06-14').locale('tzm-latn')
|
|
111
|
+
expect(getMonday(date).format(FORMAT)).to.equal('2025-06-16')
|
|
112
|
+
})
|
|
113
|
+
it('should return 2025-06-02 on 2025-06-15 (next Sunday)', function () {
|
|
114
|
+
const date = moment('2025-06-15').locale('tzm-latn')
|
|
115
|
+
expect(getMonday(date).format(FORMAT)).to.equal('2025-06-16')
|
|
116
|
+
})
|
|
117
|
+
it('should return 2025-06-02 on 2025-06-16 (next Monday)', function () {
|
|
118
|
+
const date = moment('2025-06-16').locale('tzm-latn')
|
|
119
|
+
expect(getMonday(date).format(FORMAT)).to.equal('2025-06-16')
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
})
|
package/src/actor.js
CHANGED
|
@@ -107,7 +107,7 @@ module.exports = function () {
|
|
|
107
107
|
*
|
|
108
108
|
* @param {string} locator - The locator of the element. Only accepts css selectors.
|
|
109
109
|
*/
|
|
110
|
-
waitForFocus (locator) {
|
|
110
|
+
async waitForFocus (locator) {
|
|
111
111
|
this.waitForElement(locator)
|
|
112
112
|
this.waitForVisible(locator)
|
|
113
113
|
return this.usePlaywrightTo('wait for focus', async ({ page }) => {
|
package/src/chai.js
CHANGED
|
@@ -19,9 +19,6 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import('chai').then(chai => {
|
|
22
|
-
const chaiSubset = require('chai-subset')
|
|
23
|
-
chai.use(chaiSubset)
|
|
24
|
-
|
|
25
22
|
chai.util.addProperty(chai.Assertion.prototype, 'accessible', function () {
|
|
26
23
|
const problems = ['\n', 'Accessibility Violations (' + this._obj.violations.length + ')', '---']
|
|
27
24
|
const pad = '\n '
|
package/src/contexts/contexts.js
CHANGED
|
@@ -22,7 +22,7 @@ const created = []
|
|
|
22
22
|
const users = require('../users/users')()
|
|
23
23
|
const util = require('../util')
|
|
24
24
|
const event = require('../event')
|
|
25
|
-
const contextService = require('
|
|
25
|
+
const { contextService } = require('@open-xchange/soap-client/common')
|
|
26
26
|
const crypto = require('node:crypto')
|
|
27
27
|
|
|
28
28
|
class Context {
|
package/src/contexts/reseller.js
CHANGED
|
@@ -21,8 +21,7 @@
|
|
|
21
21
|
const event = require('../event')
|
|
22
22
|
const users = require('../users/reseller')()
|
|
23
23
|
const util = require('../util')
|
|
24
|
-
const resellerContextService = require('
|
|
25
|
-
const resellerUserService = require('../soap/services/resellerUser')
|
|
24
|
+
const { resellerContextService, resellerUserService } = require('@open-xchange/soap-client/reseller')
|
|
26
25
|
const contexts = []
|
|
27
26
|
const created = []
|
|
28
27
|
|
|
@@ -19,19 +19,21 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
const { I, contactpicker, autocomplete, dialogs } = inject()
|
|
22
|
-
const
|
|
22
|
+
const { getMonday } = require('./util')
|
|
23
23
|
|
|
24
24
|
module.exports = {
|
|
25
25
|
|
|
26
26
|
editWindow: '.io-ox-calendar-edit-window',
|
|
27
27
|
miniCalendar: '.window-sidepanel .date-picker',
|
|
28
28
|
title: '[data-extension-point="io.ox/calendar/edit/section"] input[name="summary"]',
|
|
29
|
+
location: '[data-extension-point="io.ox/calendar/edit/section"] input[name="location"]',
|
|
30
|
+
description: '[data-extension-point="io.ox/calendar/edit/section"] textarea[name="description"]',
|
|
29
31
|
|
|
30
32
|
async newAppointment () {
|
|
31
33
|
I.wait(1)
|
|
32
34
|
I.clickPrimary('New appointment')
|
|
33
35
|
I.waitForVisible(this.editWindow)
|
|
34
|
-
|
|
36
|
+
return I.waitForFocus('.io-ox-calendar-edit-window input[type="text"][name="summary"]')
|
|
35
37
|
},
|
|
36
38
|
|
|
37
39
|
clickAppointment (title, position = 1) {
|
|
@@ -43,9 +45,7 @@ module.exports = {
|
|
|
43
45
|
},
|
|
44
46
|
|
|
45
47
|
getNextMonday () {
|
|
46
|
-
|
|
47
|
-
// 8 means next Monday; also over the weekend before
|
|
48
|
-
return moment().isoWeekday(8).set('hour', 8)
|
|
48
|
+
return getMonday().add(1, 'week').set('hour', 8)
|
|
49
49
|
},
|
|
50
50
|
|
|
51
51
|
startNextMonday () {
|
|
@@ -106,17 +106,14 @@ module.exports = {
|
|
|
106
106
|
}, attr)
|
|
107
107
|
},
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
addParticipant (name, exists = true, context) {
|
|
110
110
|
if (!context) context = '*'
|
|
111
|
-
if (!addedParticipants) addedParticipants = 1
|
|
112
111
|
|
|
113
112
|
// does suggestion exists (for contact, user, ...)
|
|
114
|
-
exists = typeof exists === 'boolean' ? exists : true
|
|
115
|
-
const number = await I.grabNumberOfVisibleElements(locate('.attendee').inside(context).as('Attendee')) + addedParticipants
|
|
116
113
|
const addParticipantsLocator = locate('.add-participant.tt-input').inside(context).as('Add participant field')
|
|
117
114
|
// input field
|
|
118
115
|
I.waitForVisible(addParticipantsLocator)
|
|
119
|
-
I.waitForEnabled(addParticipantsLocator)
|
|
116
|
+
// I.waitForEnabled(addParticipantsLocator)
|
|
120
117
|
I.fillField(addParticipantsLocator, name)
|
|
121
118
|
I.seeInField(addParticipantsLocator, name)
|
|
122
119
|
// tokenfield/typeahead
|
|
@@ -128,7 +125,6 @@ module.exports = {
|
|
|
128
125
|
|
|
129
126
|
I.waitForInvisible(autocomplete.suggestions)
|
|
130
127
|
// note: might be more than one that get's added (group)
|
|
131
|
-
I.waitForElement(locate('.attendee').inside(context).at(number).as(`Attendee ${number}`))
|
|
132
128
|
},
|
|
133
129
|
|
|
134
130
|
async addParticipantByPicker (name) {
|
|
@@ -139,11 +135,18 @@ module.exports = {
|
|
|
139
135
|
},
|
|
140
136
|
|
|
141
137
|
switchView (view) {
|
|
138
|
+
const viewMap = {
|
|
139
|
+
Day: 'week:day',
|
|
140
|
+
Week: 'week:week',
|
|
141
|
+
Workweek: 'week:workweek',
|
|
142
|
+
Month: 'month',
|
|
143
|
+
List: 'list'
|
|
144
|
+
}
|
|
142
145
|
I.waitForElement('.page.current .calendar-header > .dropdown button')
|
|
143
146
|
I.click('.page.current .calendar-header > .dropdown button')
|
|
144
147
|
I.waitForText(view, undefined, '.open .dropdown-menu')
|
|
145
148
|
I.click(locate('.dropdown.open a').withText(view).as('Switch to ' + view))
|
|
146
|
-
I.
|
|
149
|
+
I.waitForVisible(`.io-ox-pagecontroller.page.current[data-page-id="io.ox/calendar/${viewMap[view]}"]`)
|
|
147
150
|
},
|
|
148
151
|
|
|
149
152
|
getFullname (user) {
|
|
@@ -171,8 +174,8 @@ module.exports = {
|
|
|
171
174
|
|
|
172
175
|
if (startDate) {
|
|
173
176
|
I.click('~Date (M/D/YYYY)')
|
|
174
|
-
I.pressKey(['
|
|
175
|
-
I.
|
|
177
|
+
I.pressKey(['CommandOrControl', 'a'])
|
|
178
|
+
I.type(startDate)
|
|
176
179
|
I.pressKey('Enter')
|
|
177
180
|
}
|
|
178
181
|
|
|
@@ -183,8 +186,8 @@ module.exports = {
|
|
|
183
186
|
|
|
184
187
|
if (endDate) {
|
|
185
188
|
I.click('~Date (M/D/YYYY)', '.dateinput[data-attribute="endDate"]')
|
|
186
|
-
I.pressKey(['
|
|
187
|
-
I.
|
|
189
|
+
I.pressKey(['CommandOrControl', 'a'])
|
|
190
|
+
I.type(endDate)
|
|
188
191
|
I.pressKey('Enter')
|
|
189
192
|
}
|
|
190
193
|
|
|
@@ -23,6 +23,8 @@ const { I, dialogs } = inject()
|
|
|
23
23
|
module.exports = {
|
|
24
24
|
|
|
25
25
|
editWindow: '.io-ox-contacts-edit-window',
|
|
26
|
+
title: '.io-ox-contacts-edit-window input[name="title"]',
|
|
27
|
+
note: '.io-ox-contacts-edit-window textarea[name="note"]',
|
|
26
28
|
|
|
27
29
|
selectContact (text) {
|
|
28
30
|
I.waitForElement(`.vgrid [aria-label="${text}"]`)
|
package/src/pageobjects/mail.js
CHANGED
|
@@ -36,7 +36,6 @@ module.exports = {
|
|
|
36
36
|
I.wait(1)
|
|
37
37
|
I.click(item)
|
|
38
38
|
await I.waitForFocus('.list-view li.list-item.selected')
|
|
39
|
-
I.waitForElement('.mail-detail-frame')
|
|
40
39
|
},
|
|
41
40
|
async selectMailByIndex (index) {
|
|
42
41
|
const item = locate('.list-view li.list-item').withAttr({ 'data-index': index.toString() }).as('Mail item')
|
|
@@ -26,8 +26,7 @@ module.exports = {
|
|
|
26
26
|
I.wait(0.5) // prevent clicking a detached element caused by the bottom toolbar being re-rendered multiple times
|
|
27
27
|
I.waitForElement('~New contact')
|
|
28
28
|
I.click({ css: '[data-action="io.ox/contacts/actions/create"]' })
|
|
29
|
-
I.
|
|
30
|
-
I.waitForVisible('.io-ox-contacts-edit-window.complete')
|
|
29
|
+
I.waitForVisible('.io-ox-contacts-edit-window input[name="first_name"]')
|
|
31
30
|
I.waitForText('Add personal info')
|
|
32
31
|
},
|
|
33
32
|
|
|
@@ -18,19 +18,17 @@
|
|
|
18
18
|
* Any use of the work other than as authorized under this license or copyright law is prohibited.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
const OXaaSService = createClientAsync('OXaaSService').then(client => client)
|
|
24
|
-
|
|
25
|
-
async function setMailQuota (data) {
|
|
26
|
-
return await (await OXaaSService).setMailQuotaAsync(data)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async function createSharedDomain (data) {
|
|
30
|
-
return await (await OXaaSService).createSharedDomain(data)
|
|
31
|
-
}
|
|
21
|
+
const moment = require('moment')
|
|
32
22
|
|
|
33
23
|
module.exports = {
|
|
34
|
-
|
|
35
|
-
|
|
24
|
+
|
|
25
|
+
// return the (standard) workweek start on the current week depending on the locale
|
|
26
|
+
getMonday (date = moment()) {
|
|
27
|
+
// robust approach that also works:
|
|
28
|
+
// a) over the weekend (!)
|
|
29
|
+
// b) if firstDayOfWeek is Sunday or Saturday
|
|
30
|
+
const firstDayOfWeek = moment.localeData(date.locale()).firstDayOfWeek()
|
|
31
|
+
const localeWeekday = (8 - firstDayOfWeek) % 7
|
|
32
|
+
return date.startOf('week').weekday(localeWeekday)
|
|
33
|
+
}
|
|
36
34
|
}
|
package/src/users/reseller.js
CHANGED
|
@@ -25,8 +25,7 @@ const usersToRemove = []
|
|
|
25
25
|
const preprovisionedUsers = []
|
|
26
26
|
const util = require('../util')
|
|
27
27
|
const short = require('short-uuid')
|
|
28
|
-
const resellerUserService = require('
|
|
29
|
-
const oxaasService = require('../soap/services/oxaas')
|
|
28
|
+
const { resellerUserService, oxaasService } = require('@open-xchange/soap-client/reseller')
|
|
30
29
|
|
|
31
30
|
class User {
|
|
32
31
|
constructor (opt) {
|
package/src/users/users.js
CHANGED
|
@@ -24,7 +24,7 @@ const event = require('../event')
|
|
|
24
24
|
const users = []
|
|
25
25
|
const util = require('../util')
|
|
26
26
|
const short = require('short-uuid')
|
|
27
|
-
const userService = require('
|
|
27
|
+
const { userService } = require('@open-xchange/soap-client/common')
|
|
28
28
|
|
|
29
29
|
class User {
|
|
30
30
|
constructor (opt) {
|
package/src/util.js
CHANGED
package/chai.d.ts
DELETED
package/src/chai.d.ts
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
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 { createClientAsync, logSoapError } = require('../soap.js')
|
|
22
|
-
|
|
23
|
-
const OXContextService = createClientAsync('OXContextService').then(client => client)
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* This function retrieves the default context from the OXContextService.
|
|
27
|
-
* @returns {Promise<Object>} The first result from the listAsync method call.
|
|
28
|
-
*/
|
|
29
|
-
async function getDefault () {
|
|
30
|
-
return (await (await OXContextService).listAsync({ search_pattern: 'defaultcontext' }))[0]
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* This function removes the context with the specified ID.
|
|
35
|
-
* @param {number} id The ID of the context to remove.
|
|
36
|
-
* @returns {Promise<Boolean>} Returns true if it could remove the context and false if it could not
|
|
37
|
-
*/
|
|
38
|
-
async function remove (id) {
|
|
39
|
-
return !!await (await OXContextService).deleteAsync({ ctx: { id } }).then(() => true).catch(logSoapError)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* This function creates a new context.
|
|
44
|
-
* @param {Object} ctx The context to create.
|
|
45
|
-
* @param {Object} adminUser The admin user of the context.
|
|
46
|
-
* Defaults to:
|
|
47
|
-
* ```JSON
|
|
48
|
-
* {
|
|
49
|
-
name: 'oxadmin',
|
|
50
|
-
password: 'secret',
|
|
51
|
-
display_name: 'context admin',
|
|
52
|
-
sur_name: 'admin',
|
|
53
|
-
given_name: 'context',
|
|
54
|
-
email1: `oxadmin@${process.env.MX_DOMAIN}`,
|
|
55
|
-
primaryEmail: `oxadmin@${process.env.MX_DOMAIN}`
|
|
56
|
-
}
|
|
57
|
-
All properties are needed and will be inserted if not provided.
|
|
58
|
-
```
|
|
59
|
-
* @returns {Promise<Object>} The created context.
|
|
60
|
-
*/
|
|
61
|
-
async function create (ctx = {}, adminUser = {}) {
|
|
62
|
-
return await (await OXContextService)
|
|
63
|
-
.createAsync({
|
|
64
|
-
ctx,
|
|
65
|
-
admin_user: Object.assign({
|
|
66
|
-
name: 'oxadmin',
|
|
67
|
-
password: 'secret',
|
|
68
|
-
display_name: 'context admin',
|
|
69
|
-
sur_name: 'admin',
|
|
70
|
-
given_name: 'context',
|
|
71
|
-
email1: `oxadmin@${process.env.MX_DOMAIN}`,
|
|
72
|
-
primaryEmail: `oxadmin@${process.env.MX_DOMAIN}`
|
|
73
|
-
}, adminUser)
|
|
74
|
-
})
|
|
75
|
-
.then(async context => {
|
|
76
|
-
await changeModuleAccessByName(context.id, 'all')
|
|
77
|
-
return context
|
|
78
|
-
})
|
|
79
|
-
.catch(e => {
|
|
80
|
-
logSoapError(e)
|
|
81
|
-
throw e
|
|
82
|
-
})
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* This function changes the module access of the specified context.
|
|
87
|
-
* @param {number} id The ID of the context.
|
|
88
|
-
* @param {string} access_combination_name The name of the access combination to set.
|
|
89
|
-
* @returns {Promise<void>}
|
|
90
|
-
*/
|
|
91
|
-
// eslint-disable-next-line camelcase
|
|
92
|
-
async function changeModuleAccessByName (id, access_combination_name) {
|
|
93
|
-
// eslint-disable-next-line camelcase
|
|
94
|
-
return await (await OXContextService).changeModuleAccessByNameAsync({ ctx: { id }, access_combination_name })
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* This function changes the capabilities of the specified context.
|
|
99
|
-
* @param {number} id The ID of the context.
|
|
100
|
-
* @param {string} capsToAdd The capabilities to add.
|
|
101
|
-
* @param {string} capsToRemove The capabilities to remove.
|
|
102
|
-
* @returns {Promise<void>}
|
|
103
|
-
*/
|
|
104
|
-
async function changeCapabilities (id, capsToAdd, capsToRemove) {
|
|
105
|
-
const data = {}
|
|
106
|
-
if (capsToAdd) data.capsToAdd = capsToAdd
|
|
107
|
-
if (capsToRemove) data.capsToRemove = capsToRemove
|
|
108
|
-
return await (await OXContextService).changeCapabilitiesAsync({ ctx: { id }, ...data })
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* This function retrieves the module access of the specified context.
|
|
113
|
-
* @param {number} id The ID of the context.
|
|
114
|
-
* @returns {Promise<Object>} The module access of the specified context.
|
|
115
|
-
*/
|
|
116
|
-
async function getModuleAccess (id) {
|
|
117
|
-
return await (await OXContextService).getModuleAccessAsync({ ctx: { id } })
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* This function changes the module access of the specified context.
|
|
121
|
-
* @param {number} id The ID of the context.
|
|
122
|
-
* @param {Object} moduleAccess The module access to set.
|
|
123
|
-
* @returns {Promise<void>}
|
|
124
|
-
*/
|
|
125
|
-
async function changeModuleAccess (id, moduleAccess) {
|
|
126
|
-
const currentAccess = await getModuleAccess(id)
|
|
127
|
-
return await (await OXContextService).changeModuleAccessAsync({ ctx: { id }, access: { ...currentAccess, ...moduleAccess } })
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Search for contexts.
|
|
132
|
-
* @param {String} searchPattern The pattern to search for
|
|
133
|
-
* @param {Object} options Additional options
|
|
134
|
-
* @param {Boolean} options.excludeDisabled Exclude disabled contexts from the search results (default: true)
|
|
135
|
-
* @returns {Promise<Array<Object>>} The list of contexts that match the search pattern.
|
|
136
|
-
*/
|
|
137
|
-
async function list (searchPattern, { excludeDisabled } = { excludeDisabled: true }) {
|
|
138
|
-
return await (await OXContextService).listAsync({ search_pattern: searchPattern, exclude_disabled: excludeDisabled })
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* This function retrieves the context with the specified ID.
|
|
143
|
-
* @param {number} id The ID of the context to retrieve.
|
|
144
|
-
* @returns {Promise<Object>} The context with the specified ID.
|
|
145
|
-
*/
|
|
146
|
-
async function get (id) {
|
|
147
|
-
return await (await OXContextService).getDataAsync({ ctx: { id } })
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* This function changes the context with the specified ID.
|
|
152
|
-
* @param {Object} ctx The context to change.
|
|
153
|
-
* @returns {Promise<void>}
|
|
154
|
-
*/
|
|
155
|
-
async function change (ctx) {
|
|
156
|
-
return await (await OXContextService).changeAsync({ ctx })
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
module.exports = {
|
|
160
|
-
getDefault,
|
|
161
|
-
remove,
|
|
162
|
-
create,
|
|
163
|
-
changeModuleAccessByName,
|
|
164
|
-
changeCapabilities,
|
|
165
|
-
getModuleAccess,
|
|
166
|
-
changeModuleAccess,
|
|
167
|
-
get,
|
|
168
|
-
list,
|
|
169
|
-
change
|
|
170
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
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 { createClientAsync, logSoapError } = require('../soap.js')
|
|
22
|
-
|
|
23
|
-
const OXResellerContextService = createClientAsync('OXResellerContextService').then(client => client)
|
|
24
|
-
|
|
25
|
-
async function create (data) {
|
|
26
|
-
return await (await OXResellerContextService).createAsync(data)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async function remove (contextId) {
|
|
30
|
-
return !!await (await OXResellerContextService).deleteAsync({
|
|
31
|
-
ctx: { id: contextId }
|
|
32
|
-
}).then(() => true).catch(logSoapError)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async function change (ctx) {
|
|
36
|
-
return await (await OXResellerContextService).changeAsync({ ctx })
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async function get (id) {
|
|
40
|
-
return await (await OXResellerContextService).getDataAsync({ ctx: { id } })
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async function getModuleAccess (id) {
|
|
44
|
-
return await (await OXResellerContextService).getModuleAccessAsync({ ctx: id })
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async function changeModuleAccess (id, access) {
|
|
48
|
-
const currentAccess = await getModuleAccess(id)
|
|
49
|
-
|
|
50
|
-
return await (await OXResellerContextService).changeModuleAccessAsync({ ctx: { id }, access: { ...currentAccess, ...access } })
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async function listAll (data) {
|
|
54
|
-
return await (await OXResellerContextService).listAllAsync(data)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
module.exports = {
|
|
58
|
-
create,
|
|
59
|
-
remove,
|
|
60
|
-
change,
|
|
61
|
-
get,
|
|
62
|
-
getModuleAccess,
|
|
63
|
-
changeModuleAccess,
|
|
64
|
-
listAll
|
|
65
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
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 { createClientAsync, logSoapError } = require('../soap.js')
|
|
22
|
-
|
|
23
|
-
const OXResellerUserService = createClientAsync('OXResellerUserService').then(client => client)
|
|
24
|
-
|
|
25
|
-
async function remove (contextId, userId) {
|
|
26
|
-
return !!await (await OXResellerUserService).deleteAsync({
|
|
27
|
-
ctx: { id: contextId },
|
|
28
|
-
user: { id: userId }
|
|
29
|
-
}).then(() => true).catch(logSoapError)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* This function changes the context with the specified ID.
|
|
34
|
-
* @param {Object} context The context of the user to be changed.
|
|
35
|
-
* @param {Object} usrdata The user to change.
|
|
36
|
-
* @returns {Promise<void>}
|
|
37
|
-
*/
|
|
38
|
-
async function change (context, usrdata) {
|
|
39
|
-
return await (await OXResellerUserService).changeAsync({
|
|
40
|
-
ctx: { id: context.id },
|
|
41
|
-
usrdata,
|
|
42
|
-
auth: { login: context.admin.name, password: context.admin.password }
|
|
43
|
-
})
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async function get (data) {
|
|
47
|
-
return await (await OXResellerUserService).getDataAsync(data)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async function getModuleAccess (context, userId) {
|
|
51
|
-
return await (await OXResellerUserService).getModuleAccessAsync({
|
|
52
|
-
ctx: { id: context.id },
|
|
53
|
-
user: { id: userId },
|
|
54
|
-
auth: { login: context.admin.name, password: context.admin.password }
|
|
55
|
-
}).then(
|
|
56
|
-
(res) => res
|
|
57
|
-
)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function changeByModuleAccess (context, userId, currentAccess, moduleAccess) {
|
|
61
|
-
return await (await OXResellerUserService).changeByModuleAccessAsync({
|
|
62
|
-
ctx: { id: context.id },
|
|
63
|
-
moduleAccess: Object.assign({}, currentAccess, moduleAccess),
|
|
64
|
-
user: { id: userId },
|
|
65
|
-
auth: { login: context.admin.name, password: context.admin.password }
|
|
66
|
-
})
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async function changeByModuleAccessName (context, userId, accessCombinationName) {
|
|
70
|
-
return await (await OXResellerUserService).changeByModuleAccessNameAsync({
|
|
71
|
-
ctx: { id: context.id },
|
|
72
|
-
access_combination_name: accessCombinationName,
|
|
73
|
-
user: { id: userId },
|
|
74
|
-
auth: { login: context.admin.name, password: context.admin.password }
|
|
75
|
-
}).catch(error => console.error('Module access change error: ', error))
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async function createByModuleAccessName (context, usrdata) {
|
|
79
|
-
return await (await OXResellerUserService).createByModuleAccessNameAsync({
|
|
80
|
-
ctx: { id: context.id },
|
|
81
|
-
usrdata,
|
|
82
|
-
access_combination_name: 'all',
|
|
83
|
-
auth: { login: context.admin.name, password: context.admin.password }
|
|
84
|
-
})
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
async function listAll (data) {
|
|
88
|
-
return await (await OXResellerUserService).listAllAsync(data)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
module.exports = {
|
|
92
|
-
remove,
|
|
93
|
-
change,
|
|
94
|
-
get,
|
|
95
|
-
getModuleAccess,
|
|
96
|
-
changeByModuleAccess,
|
|
97
|
-
changeByModuleAccessName,
|
|
98
|
-
createByModuleAccessName,
|
|
99
|
-
listAll
|
|
100
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
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 { createClientAsync, logSoapError } = require('../soap.js')
|
|
22
|
-
|
|
23
|
-
const OXUserService = createClientAsync('OXUserService').then(client => client)
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* This function removes the user with the specified context and user ID.
|
|
27
|
-
* @param {Object} context The context of the user to be removed.
|
|
28
|
-
* @param {number} userId The ID of the user to be removed.
|
|
29
|
-
* @returns {Promise<Boolean>} Returns true if it could remove the user and false if it could not
|
|
30
|
-
*/
|
|
31
|
-
async function remove (context, userId) {
|
|
32
|
-
return !!await (await OXUserService).deleteAsync({
|
|
33
|
-
ctx: { id: context.id },
|
|
34
|
-
user: { id: userId },
|
|
35
|
-
auth: context.admin
|
|
36
|
-
}).then(() => true).catch(logSoapError)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* This function creates a new user.
|
|
41
|
-
* @param {Object} context The context of the user to be created.
|
|
42
|
-
* @param {Object} usrdata The user to create.
|
|
43
|
-
* @returns {Promise<Object>} The created user.
|
|
44
|
-
*/
|
|
45
|
-
async function create (context, usrdata) {
|
|
46
|
-
return await (await OXUserService).createAsync({
|
|
47
|
-
ctx: { id: context.id },
|
|
48
|
-
usrdata,
|
|
49
|
-
auth: context.admin
|
|
50
|
-
})
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* This function changes the context with the specified ID.
|
|
55
|
-
* @param {Object} context The context of the user to be changed.
|
|
56
|
-
* @param {Object} usrdata The user to change.
|
|
57
|
-
* @returns {Promise<void>}
|
|
58
|
-
*/
|
|
59
|
-
async function change (context, usrdata) {
|
|
60
|
-
return await (await OXUserService).changeAsync({
|
|
61
|
-
ctx: { id: context.id },
|
|
62
|
-
usrdata,
|
|
63
|
-
auth: context.admin
|
|
64
|
-
})
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function changeByModuleAccess (context, userId, currentAccess, moduleAccess) {
|
|
68
|
-
return await (await OXUserService).changeByModuleAccessAsync({
|
|
69
|
-
ctx: { id: context.id },
|
|
70
|
-
moduleAccess: Object.assign({}, currentAccess, moduleAccess),
|
|
71
|
-
user: { id: userId },
|
|
72
|
-
auth: context.admin
|
|
73
|
-
})
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async function changeByModuleAccessName (context, userId, accessCombinationName) {
|
|
77
|
-
return await (await OXUserService).changeByModuleAccessNameAsync({
|
|
78
|
-
ctx: { id: context.id },
|
|
79
|
-
access_combination_name: accessCombinationName,
|
|
80
|
-
user: { id: userId },
|
|
81
|
-
auth: context.admin
|
|
82
|
-
}).catch(error => console.error('Module access change error:', error))
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async function getModuleAccess (context, userId) {
|
|
86
|
-
return await (await OXUserService).getModuleAccessAsync({
|
|
87
|
-
ctx: { id: context.id },
|
|
88
|
-
user: { id: userId },
|
|
89
|
-
auth: context.admin
|
|
90
|
-
}).then(
|
|
91
|
-
(res) => res
|
|
92
|
-
)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
async function changeCapabilities (context, userId, capsToAdd, capsToRemove) {
|
|
96
|
-
const data = {
|
|
97
|
-
ctx: { id: context.id },
|
|
98
|
-
user: { id: userId },
|
|
99
|
-
auth: context.admin
|
|
100
|
-
}
|
|
101
|
-
if (capsToAdd) data.capsToAdd = capsToAdd
|
|
102
|
-
if (capsToRemove) data.capsToRemove = capsToRemove
|
|
103
|
-
return await (await OXUserService).changeCapabilitiesAsync(data)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
module.exports = {
|
|
107
|
-
create,
|
|
108
|
-
change,
|
|
109
|
-
changeByModuleAccess,
|
|
110
|
-
changeByModuleAccessName,
|
|
111
|
-
changeCapabilities,
|
|
112
|
-
getModuleAccess,
|
|
113
|
-
remove
|
|
114
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
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 { createClientAsync } = require('../soap.js')
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* This function retrieves the ID of the first filestorage.
|
|
25
|
-
* @returns {Promise<number>} The ID of the first filestorage.
|
|
26
|
-
*/
|
|
27
|
-
let fileStoreId
|
|
28
|
-
|
|
29
|
-
async function getFilestorageId () {
|
|
30
|
-
const OXUtilService = createClientAsync('OXUtilService').then(client => client)
|
|
31
|
-
|
|
32
|
-
if (fileStoreId) return fileStoreId
|
|
33
|
-
fileStoreId = (await (await OXUtilService).listFilestoreAsync())[0]?.id
|
|
34
|
-
return fileStoreId
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
module.exports = {
|
|
38
|
-
getFilestorageId
|
|
39
|
-
}
|
package/src/soap/soap.js
DELETED
|
@@ -1,176 +0,0 @@
|
|
|
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 { performance, PerformanceObserver } = require('node:perf_hooks')
|
|
22
|
-
const SOAP = require('soap')
|
|
23
|
-
const dotenv = require('dotenv')
|
|
24
|
-
|
|
25
|
-
let AbortError
|
|
26
|
-
const pRetry = import('p-retry').then(module => {
|
|
27
|
-
AbortError = module.AbortError
|
|
28
|
-
return module.default
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
dotenv.config({ path: '.env' })
|
|
32
|
-
dotenv.config({ path: '.env.defaults' })
|
|
33
|
-
|
|
34
|
-
// This flag enables debug output including timing information.
|
|
35
|
-
const debug = process.env.DEBUG_SOAP === 'true'
|
|
36
|
-
|
|
37
|
-
// This URL is used to create the SOAP client.
|
|
38
|
-
const provisioningUrl = String(process.env.PROVISIONING_URL).replace(/\/$/, '')
|
|
39
|
-
|
|
40
|
-
// This user is used to authenticate against the provisioning API.
|
|
41
|
-
const provisioningAuth = {
|
|
42
|
-
auth: {
|
|
43
|
-
login: process.env.E2E_ADMIN_USER,
|
|
44
|
-
password: process.env.E2E_ADMIN_PW
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* This code creates a PerformanceObserver instance which asynchronously observes
|
|
50
|
-
* performance measurement events. It logs the name of the event (which is the name
|
|
51
|
-
* of the SOAP method in this context) and the time it took to execute in milliseconds.
|
|
52
|
-
*
|
|
53
|
-
* This is used for performance tracking of the SOAP methods.
|
|
54
|
-
* @see https://nodejs.org/api/perf_hooks.html
|
|
55
|
-
* @params {PerformanceObserverEntryList} items The list of performance entries.
|
|
56
|
-
* @returns {void}
|
|
57
|
-
*/
|
|
58
|
-
const obs = new PerformanceObserver((items) => {
|
|
59
|
-
items.getEntries().forEach((entry) => {
|
|
60
|
-
if (debug) console.log(`${entry.name} took ${Math.round(entry.duration)}ms`)
|
|
61
|
-
})
|
|
62
|
-
})
|
|
63
|
-
obs.observe({ entryTypes: ['measure'] })
|
|
64
|
-
|
|
65
|
-
async function logSoapError (e) {
|
|
66
|
-
console.error(e?.originalError?.root?.Envelope?.Body?.Fault?.faultstring || e.message)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* This function checks if the specified error should abort the retry.
|
|
71
|
-
* @param {Error} error The error to check.
|
|
72
|
-
* @returns {boolean} True if the error should abort the retry, false otherwise.
|
|
73
|
-
**/
|
|
74
|
-
function shouldAbortRetry (error) {
|
|
75
|
-
try {
|
|
76
|
-
const fault = error.root.Envelope.Body.Fault
|
|
77
|
-
const details = fault.detail
|
|
78
|
-
const blockedFaultStrings = [
|
|
79
|
-
/Context \d+ already exists/,
|
|
80
|
-
/Authentication failed/,
|
|
81
|
-
/Context \d+ does not exist/,
|
|
82
|
-
/CloudPluginException: username .* already exists/,
|
|
83
|
-
/CloudPluginException: context name must begin/,
|
|
84
|
-
/The displayname is already used/,
|
|
85
|
-
/already exists in this context/,
|
|
86
|
-
/Shared Domain already exists/,
|
|
87
|
-
/Shared Domain already in use/,
|
|
88
|
-
/No such user/,
|
|
89
|
-
/Mandatory fields in context not set/,
|
|
90
|
-
/A mapping with login info .* already exists/,
|
|
91
|
-
/Id must not be set if pre-assembled context should be used/,
|
|
92
|
-
/Unmarshalling Error/
|
|
93
|
-
]
|
|
94
|
-
const blockedExceptions = [
|
|
95
|
-
'ContextExistsException',
|
|
96
|
-
'InvalidCredentialsException',
|
|
97
|
-
'NoSuchContextException'
|
|
98
|
-
]
|
|
99
|
-
// return "details object contains any of the blocked items"
|
|
100
|
-
return blockedFaultStrings.some(msg => msg.test(fault.faultstring)) ||
|
|
101
|
-
blockedExceptions.some(exception => Object.hasOwnProperty.call(details, exception))
|
|
102
|
-
} catch (e) {
|
|
103
|
-
// some other error which is never blocked
|
|
104
|
-
return false
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* This function creates a SOAP client for the specified service type.
|
|
110
|
-
* @param {string} type The name of the service type.
|
|
111
|
-
* @returns {Promise<Object>} The SOAP client.
|
|
112
|
-
*/
|
|
113
|
-
async function createClientAsync (type) {
|
|
114
|
-
const startMark = `${type}-start`
|
|
115
|
-
const endMark = `${type}-end`
|
|
116
|
-
performance.mark(startMark)
|
|
117
|
-
const endpoint = `${provisioningUrl}/webservices/${type}`
|
|
118
|
-
const url = `${endpoint}/?wsdl`
|
|
119
|
-
const client = await SOAP.createClientAsync(url, {
|
|
120
|
-
endpoint,
|
|
121
|
-
suppressStack: true,
|
|
122
|
-
wsdl_options: {
|
|
123
|
-
forever: true
|
|
124
|
-
},
|
|
125
|
-
gzip: true
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
// https://stackoverflow.com/questions/30740415/namespace-for-array-field-in-node-soap-client-node-js
|
|
129
|
-
client.wsdl.definitions.xmlns.ns1 = 'http://dataobjects.soap.admin.openexchange.com/xsd'
|
|
130
|
-
client.wsdl.xmlnsInEnvelope = client.wsdl._xmlnsMap()
|
|
131
|
-
performance.mark(endMark)
|
|
132
|
-
performance.measure(` ⏱ SOAP: ${type} -> createClientAsync`, startMark, endMark)
|
|
133
|
-
|
|
134
|
-
// This proxy effectively adds error handling, authentication and performance measurements to all methods of the SOAP client.
|
|
135
|
-
return new Proxy(client, {
|
|
136
|
-
get (target, prop, receiver) {
|
|
137
|
-
const origMethod = Reflect.get(target, prop, receiver)
|
|
138
|
-
if (typeof origMethod === 'function') {
|
|
139
|
-
return async function (options, clientOptions, ...args) {
|
|
140
|
-
const startMark = `${prop}-start`
|
|
141
|
-
const endMark = `${prop}-end`
|
|
142
|
-
performance.mark(startMark)
|
|
143
|
-
const soapOptions = { ...provisioningAuth, ...options }
|
|
144
|
-
if (soapOptions.auth) {
|
|
145
|
-
// only send login and password instead of complete admin object.
|
|
146
|
-
// this can fail because of ambiguous namespacing
|
|
147
|
-
soapOptions.auth = { login: soapOptions.auth.login, password: soapOptions.auth.password }
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const result = await (await pRetry)(() => origMethod.apply(this, [soapOptions, { timeout: 10000, ...clientOptions }, ...args]), {
|
|
151
|
-
retries: 3,
|
|
152
|
-
onFailedAttempt: async error => {
|
|
153
|
-
if (shouldAbortRetry(error)) throw new (await AbortError)(error)
|
|
154
|
-
console.log(`Attempt ${error.attemptNumber} failed. There are ${error.retriesLeft} retries left.`)
|
|
155
|
-
}
|
|
156
|
-
}).catch(e => {
|
|
157
|
-
const soapError = e?.originalError?.root?.Envelope?.Body?.Fault
|
|
158
|
-
throw new Error(soapError?.faultstring || e.message)
|
|
159
|
-
})
|
|
160
|
-
performance.mark(endMark)
|
|
161
|
-
performance.measure(` ⏱ SOAP: ${type} -> ${String(prop)}`, startMark, endMark)
|
|
162
|
-
// Return only the first result from the SOAP method call, as we don't need the SOAP envelope and other stuff.
|
|
163
|
-
if (!result || !result[0]) return
|
|
164
|
-
return result[0]?.return
|
|
165
|
-
}
|
|
166
|
-
} else {
|
|
167
|
-
return origMethod
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
})
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
module.exports = {
|
|
174
|
-
createClientAsync,
|
|
175
|
-
logSoapError
|
|
176
|
-
}
|