eionet2-dashboard 3.2.1 → 3.2.2
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/CHANGELOG.md +23 -0
- package/Jenkinsfile +2 -2
- package/api/getGraphData/index.js +3 -4
- package/package.json +2 -2
- package/tabs/package-lock.json +2894 -1833
- package/tabs/package.json +3 -3
- package/tabs/public/auth-start.html +2 -2
- package/tabs/src/components/App.test.jsx +67 -0
- package/tabs/src/components/BottomMenu.test.jsx +94 -0
- package/tabs/src/components/CustomColumnResizeIcon.test.jsx +17 -0
- package/tabs/src/components/CustomDrawer.test.jsx +14 -0
- package/tabs/src/components/CustomGridToolbar.test.jsx +8 -0
- package/tabs/src/components/EventDialogTitle.test.jsx +25 -0
- package/tabs/src/components/HtmlBox.test.jsx +41 -0
- package/tabs/src/components/Privacy.test.jsx +11 -0
- package/tabs/src/components/ResizableGrid.test.jsx +64 -0
- package/tabs/src/components/Tab.test.jsx +463 -0
- package/tabs/src/components/TabConfig.test.jsx +27 -0
- package/tabs/src/components/TabPanel.test.jsx +31 -0
- package/tabs/src/components/TermsOfUse.test.jsx +11 -0
- package/tabs/src/components/UnderConstruction.test.jsx +13 -0
- package/tabs/src/components/UserMenu.test.jsx +53 -0
- package/tabs/src/components/activity/Activity.test.jsx +218 -0
- package/tabs/src/components/activity/ConsultationList.test.jsx +114 -0
- package/tabs/src/components/activity/EventList.test.jsx +164 -0
- package/tabs/src/components/activity/GroupsTags.test.jsx +23 -0
- package/tabs/src/components/activity/ObligationList.test.jsx +46 -0
- package/tabs/src/components/activity/PublicationList.test.jsx +46 -0
- package/tabs/src/components/activity/Reporting.test.jsx +11 -0
- package/tabs/src/components/event_rating/EventRating.test.jsx +63 -0
- package/tabs/src/components/event_rating/EventRatingDialog.test.jsx +28 -0
- package/tabs/src/components/event_registration/Approval.test.jsx +25 -0
- package/tabs/src/components/event_registration/ApprovalDialog.test.jsx +25 -0
- package/tabs/src/components/event_registration/ApprovalList.test.jsx +36 -0
- package/tabs/src/components/event_registration/EventExternalRegistration.test.jsx +219 -0
- package/tabs/src/components/event_registration/EventRegistration.test.jsx +208 -0
- package/tabs/src/components/my_country/AtAGlance.test.jsx +157 -0
- package/tabs/src/components/my_country/CountryMembers.test.jsx +117 -0
- package/tabs/src/components/my_country/CountryProgress.test.jsx +21 -0
- package/tabs/src/components/my_country/DataReporters.test.jsx +156 -0
- package/tabs/src/components/my_country/FullCircularProgress.test.jsx +19 -0
- package/tabs/src/components/my_country/GroupView.test.jsx +165 -0
- package/tabs/src/components/my_country/GroupsBoard.test.jsx +30 -0
- package/tabs/src/components/my_country/IndicatorCard.test.jsx +27 -0
- package/tabs/src/components/my_country/ManagementBoard.test.jsx +119 -0
- package/tabs/src/components/my_country/MyCountry.test.jsx +220 -0
- package/tabs/src/components/my_country/ProgressGauge.test.jsx +34 -0
- package/tabs/src/components/my_country/ScientificCommittee.test.jsx +11 -0
- package/tabs/src/components/my_country/UserCard.test.jsx +24 -0
- package/tabs/src/components/my_country/YearlyProgress.test.jsx +33 -0
- package/tabs/src/components/self_service/UserEdit.test.jsx +213 -0
- package/tabs/src/data/apiProvider.test.js +228 -0
- package/tabs/src/data/icsHelper.test.js +76 -0
- package/tabs/src/data/provider.test.js +351 -0
- package/tabs/src/data/reportingProvider.test.js +103 -0
- package/tabs/src/data/selfServiceProvider.test.js +108 -0
- package/tabs/src/data/selfServiceSharepointProvider.test.js +100 -0
- package/tabs/src/data/sharepointProvider.test.js +669 -0
- package/tabs/src/data/validator.test.js +34 -2
- package/tabs/yarn.lock +415 -414
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
jest.mock('@microsoft/teams-js', () => ({
|
|
2
|
+
app: {
|
|
3
|
+
initialize: jest.fn().mockResolvedValue(undefined),
|
|
4
|
+
},
|
|
5
|
+
authentication: {
|
|
6
|
+
getAuthToken: jest.fn().mockResolvedValue('token-123'),
|
|
7
|
+
},
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
const mockRequest = jest.fn();
|
|
11
|
+
jest.mock('axios', () => ({
|
|
12
|
+
__esModule: true,
|
|
13
|
+
request: mockRequest,
|
|
14
|
+
default: {
|
|
15
|
+
request: mockRequest,
|
|
16
|
+
},
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
describe('apiProvider', () => {
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
jest.clearAllMocks();
|
|
22
|
+
jest.resetModules();
|
|
23
|
+
process.env.REACT_APP_FUNC_ENDPOINT = 'https://func.example.org';
|
|
24
|
+
process.env.REACT_APP_SHAREPOINT_SITE_ID = 'site-id';
|
|
25
|
+
process.env.REACT_APP_CONFIGURATION_LIST_ID = 'cfg-list-id';
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('apiGet calls function endpoint with auth token and query params', async () => {
|
|
29
|
+
const axios = require('axios');
|
|
30
|
+
const { apiGet } = require('./apiProvider');
|
|
31
|
+
|
|
32
|
+
axios.default.request.mockResolvedValue({
|
|
33
|
+
data: { graphClientMessage: { value: [] } },
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const response = await apiGet('/users', 'user', true);
|
|
37
|
+
|
|
38
|
+
expect(response).toEqual({ graphClientMessage: { value: [] } });
|
|
39
|
+
expect(axios.default.request).toHaveBeenCalledWith({
|
|
40
|
+
method: 'get',
|
|
41
|
+
url: 'https://func.example.org/api/graphData',
|
|
42
|
+
headers: {
|
|
43
|
+
authorization: 'Bearer token-123',
|
|
44
|
+
},
|
|
45
|
+
data: undefined,
|
|
46
|
+
params: {
|
|
47
|
+
path: '/users',
|
|
48
|
+
credentialType: 'user',
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('apiPatch includes eTag when provided', async () => {
|
|
54
|
+
const axios = require('axios');
|
|
55
|
+
const { apiPatch } = require('./apiProvider');
|
|
56
|
+
|
|
57
|
+
axios.default.request.mockResolvedValue({
|
|
58
|
+
data: { ok: true },
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await apiPatch('/sites/abc', { key: 'value' }, 'etag-value');
|
|
62
|
+
|
|
63
|
+
expect(axios.default.request).toHaveBeenCalledWith(
|
|
64
|
+
expect.objectContaining({
|
|
65
|
+
method: 'patch',
|
|
66
|
+
data: {
|
|
67
|
+
credentialType: 'app',
|
|
68
|
+
data: { key: 'value' },
|
|
69
|
+
path: '/sites/abc',
|
|
70
|
+
eTag: 'etag-value',
|
|
71
|
+
},
|
|
72
|
+
}),
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('getUserMail caches user email after first request', async () => {
|
|
77
|
+
const axios = require('axios');
|
|
78
|
+
const { getUserMail } = require('./apiProvider');
|
|
79
|
+
|
|
80
|
+
axios.default.request.mockResolvedValue({
|
|
81
|
+
data: {
|
|
82
|
+
graphClientMessage: {
|
|
83
|
+
mail: 'cached@example.org',
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const first = await getUserMail();
|
|
89
|
+
const second = await getUserMail();
|
|
90
|
+
|
|
91
|
+
expect(first).toBe('cached@example.org');
|
|
92
|
+
expect(second).toBe('cached@example.org');
|
|
93
|
+
expect(axios.default.request).toHaveBeenCalledTimes(1);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('getConfiguration caches mapped SharePoint config', async () => {
|
|
97
|
+
const axios = require('axios');
|
|
98
|
+
const { getConfiguration } = require('./apiProvider');
|
|
99
|
+
|
|
100
|
+
axios.default.request.mockResolvedValue({
|
|
101
|
+
data: {
|
|
102
|
+
graphClientMessage: {
|
|
103
|
+
value: [
|
|
104
|
+
{ fields: { Title: 'LoggingListId', Value: 'log-list-id' } },
|
|
105
|
+
{ fields: { Title: 'FromEmailAddress', Value: 'from@example.org' } },
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const first = await getConfiguration();
|
|
112
|
+
const second = await getConfiguration();
|
|
113
|
+
|
|
114
|
+
expect(first).toEqual({
|
|
115
|
+
LoggingListId: 'log-list-id',
|
|
116
|
+
FromEmailAddress: 'from@example.org',
|
|
117
|
+
SharepointSiteId: 'site-id',
|
|
118
|
+
});
|
|
119
|
+
expect(second).toEqual(first);
|
|
120
|
+
expect(axios.default.request).toHaveBeenCalledTimes(1);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('apiGet rethrows and skips logging when requiresLogin is true', async () => {
|
|
124
|
+
const axios = require('axios');
|
|
125
|
+
const { apiGet } = require('./apiProvider');
|
|
126
|
+
const err = { requiresLogin: true };
|
|
127
|
+
|
|
128
|
+
axios.default.request.mockRejectedValue(err);
|
|
129
|
+
|
|
130
|
+
await expect(apiGet('/users')).rejects.toBe(err);
|
|
131
|
+
expect(axios.default.request).toHaveBeenCalledTimes(1);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('apiPost rethrows and skips logging when skipLog is true', async () => {
|
|
135
|
+
const axios = require('axios');
|
|
136
|
+
const { apiPost } = require('./apiProvider');
|
|
137
|
+
const err = new Error('post failed');
|
|
138
|
+
|
|
139
|
+
axios.default.request.mockRejectedValue(err);
|
|
140
|
+
|
|
141
|
+
await expect(apiPost('/users', { a: 1 }, 'app', true)).rejects.toBe(err);
|
|
142
|
+
expect(axios.default.request).toHaveBeenCalledTimes(1);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('logError uses missing-index message and writes to logging list when config exists', async () => {
|
|
146
|
+
const axios = require('axios');
|
|
147
|
+
const { getConfiguration, logError } = require('./apiProvider');
|
|
148
|
+
|
|
149
|
+
axios.default.request
|
|
150
|
+
.mockResolvedValueOnce({
|
|
151
|
+
data: {
|
|
152
|
+
graphClientMessage: {
|
|
153
|
+
value: [{ fields: { Title: 'LoggingListId', Value: 'log-list-id' } }],
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
})
|
|
157
|
+
.mockResolvedValueOnce({
|
|
158
|
+
data: {
|
|
159
|
+
graphClientMessage: {
|
|
160
|
+
mail: 'user@example.org',
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
})
|
|
164
|
+
.mockResolvedValueOnce({
|
|
165
|
+
data: { ok: true },
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
await getConfiguration();
|
|
169
|
+
|
|
170
|
+
await logError(
|
|
171
|
+
{
|
|
172
|
+
message: 'fallback',
|
|
173
|
+
response: {
|
|
174
|
+
data: {
|
|
175
|
+
message: 'HonorNonIndexedQueriesWarningMayFailRandomly details',
|
|
176
|
+
error: { body: 'ignored body' },
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
'/path',
|
|
181
|
+
{ key: 'value' },
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const lastCall = axios.default.request.mock.calls.at(-1)[0];
|
|
185
|
+
expect(lastCall.method).toBe('post');
|
|
186
|
+
expect(lastCall.data.path).toBe('/sites/site-id/lists/log-list-id/items');
|
|
187
|
+
expect(lastCall.data.data.fields.Title).toBe(
|
|
188
|
+
'HonorNonIndexedQueriesWarningMayFailRandomly details',
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test('logInfo can omit user email when skipEmail is true', async () => {
|
|
193
|
+
const axios = require('axios');
|
|
194
|
+
const { logInfo } = require('./apiProvider');
|
|
195
|
+
|
|
196
|
+
axios.default.request
|
|
197
|
+
.mockResolvedValueOnce({
|
|
198
|
+
data: {
|
|
199
|
+
graphClientMessage: {
|
|
200
|
+
value: [{ fields: { Title: 'LoggingListId', Value: 'log-list-id' } }],
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
})
|
|
204
|
+
.mockResolvedValueOnce({
|
|
205
|
+
data: {
|
|
206
|
+
graphClientMessage: {
|
|
207
|
+
mail: 'user@example.org',
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
})
|
|
211
|
+
.mockResolvedValueOnce({
|
|
212
|
+
data: { ok: true },
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
await logInfo('Info title', '/path', { key: 1 }, 'ACT', 'affected@example.org', true);
|
|
216
|
+
|
|
217
|
+
const lastCall = axios.default.request.mock.calls.at(-1)[0];
|
|
218
|
+
expect(lastCall.data.path).toBe('/sites/site-id/lists/log-list-id/items');
|
|
219
|
+
expect(lastCall.data.data.fields).toMatchObject({
|
|
220
|
+
Title: 'Info title',
|
|
221
|
+
ApiPath: '/path',
|
|
222
|
+
Action: 'ACT',
|
|
223
|
+
AffectedUser: 'affected@example.org',
|
|
224
|
+
UserMail: '',
|
|
225
|
+
Logtype: 'Info',
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
jest.mock('ics', () => ({
|
|
2
|
+
createEvent: jest.fn(),
|
|
3
|
+
}));
|
|
4
|
+
|
|
5
|
+
const { createEvent } = require('ics');
|
|
6
|
+
const { createIcs } = require('./icsHelper');
|
|
7
|
+
|
|
8
|
+
describe('createIcs', () => {
|
|
9
|
+
let originalBlob;
|
|
10
|
+
|
|
11
|
+
beforeAll(() => {
|
|
12
|
+
originalBlob = global.Blob;
|
|
13
|
+
if (typeof global.Blob === 'undefined') {
|
|
14
|
+
global.Blob = function Blob(parts, options) {
|
|
15
|
+
this.parts = parts;
|
|
16
|
+
this.type = options?.type || '';
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterAll(() => {
|
|
22
|
+
global.Blob = originalBlob;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
jest.clearAllMocks();
|
|
27
|
+
createEvent.mockImplementation((event, callback) => {
|
|
28
|
+
callback(null, 'BEGIN:VCALENDAR');
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('creates an event with hour/minute duration and meeting link', () => {
|
|
33
|
+
const meeting = {
|
|
34
|
+
MeetingStart: '2025-01-01T10:00:00.000Z',
|
|
35
|
+
MeetingEnd: '2025-01-01T11:30:00.000Z',
|
|
36
|
+
Title: 'Coordination meeting',
|
|
37
|
+
MeetingLink: 'https://example.org/meeting',
|
|
38
|
+
};
|
|
39
|
+
const participant = {
|
|
40
|
+
ParticipantName: 'Jane Doe',
|
|
41
|
+
Email: 'jane@example.org',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const blob = createIcs(meeting, 'organizer@example.org', 'Line 1\nLine 2', participant);
|
|
45
|
+
|
|
46
|
+
expect(createEvent).toHaveBeenCalledWith(
|
|
47
|
+
expect.objectContaining({
|
|
48
|
+
method: 'REQUEST',
|
|
49
|
+
title: 'Coordination meeting',
|
|
50
|
+
duration: { hours: 1, minutes: 30 },
|
|
51
|
+
htmlContent: 'Line 1Line 2',
|
|
52
|
+
url: 'https://example.org/meeting',
|
|
53
|
+
}),
|
|
54
|
+
expect.any(Function),
|
|
55
|
+
);
|
|
56
|
+
expect(blob).toBeInstanceOf(Blob);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('creates an event with minutes-only duration and no url when link is missing', () => {
|
|
60
|
+
const meeting = {
|
|
61
|
+
MeetingStart: '2025-01-01T10:00:00.000Z',
|
|
62
|
+
MeetingEnd: '2025-01-01T10:45:00.000Z',
|
|
63
|
+
Title: 'Short meeting',
|
|
64
|
+
};
|
|
65
|
+
const participant = {
|
|
66
|
+
ParticipantName: 'John Doe',
|
|
67
|
+
Email: 'john@example.org',
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
createIcs(meeting, 'organizer@example.org', 'Description', participant);
|
|
71
|
+
|
|
72
|
+
const eventArg = createEvent.mock.calls[0][0];
|
|
73
|
+
expect(eventArg.duration).toEqual({ minutes: 45 });
|
|
74
|
+
expect(eventArg.url).toBeUndefined();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
jest.mock('./apiProvider', () => ({
|
|
2
|
+
apiGet: jest.fn(),
|
|
3
|
+
apiPost: jest.fn(),
|
|
4
|
+
getConfiguration: jest.fn(),
|
|
5
|
+
logInfo: jest.fn(),
|
|
6
|
+
logError: jest.fn(),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
jest.mock('./sharepointProvider', () => ({
|
|
10
|
+
getSPUserByMail: jest.fn(),
|
|
11
|
+
getMeetingManager: jest.fn(),
|
|
12
|
+
getOrganisationList: jest.fn(),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
const { apiGet, apiPost, getConfiguration, logInfo, logError } = require('./apiProvider');
|
|
16
|
+
const { getSPUserByMail, getMeetingManager, getOrganisationList } = require('./sharepointProvider');
|
|
17
|
+
const { getUserByMail, sendEmail, getMeetingJoinInfo, getUser } = require('./provider');
|
|
18
|
+
|
|
19
|
+
describe('provider', () => {
|
|
20
|
+
const loadProvider = () => {
|
|
21
|
+
let loaded;
|
|
22
|
+
jest.isolateModules(() => {
|
|
23
|
+
loaded = require('./provider');
|
|
24
|
+
});
|
|
25
|
+
return loaded;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
jest.clearAllMocks();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('getUserByMail returns AD and SharePoint data and escapes apostrophe in email', async () => {
|
|
33
|
+
apiGet.mockResolvedValue({
|
|
34
|
+
graphClientMessage: {
|
|
35
|
+
value: [{ id: 'ad-user' }],
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
getSPUserByMail.mockResolvedValue({ id: 'sp-user' });
|
|
39
|
+
|
|
40
|
+
const result = await getUserByMail("o'hara@example.org");
|
|
41
|
+
|
|
42
|
+
expect(apiGet).toHaveBeenCalledWith("/users/?$filter=mail eq 'o''hara%40example.org'");
|
|
43
|
+
expect(getSPUserByMail).toHaveBeenCalledWith("o'hara@example.org");
|
|
44
|
+
expect(result).toEqual({
|
|
45
|
+
ADUser: { id: 'ad-user' },
|
|
46
|
+
SharepointUser: { id: 'sp-user' },
|
|
47
|
+
IsValid: true,
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('getUserByMail returns invalid when AD user list is empty', async () => {
|
|
52
|
+
apiGet.mockResolvedValue({
|
|
53
|
+
graphClientMessage: {
|
|
54
|
+
value: [],
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
getSPUserByMail.mockResolvedValue(undefined);
|
|
58
|
+
|
|
59
|
+
const result = await getUserByMail('missing@example.org');
|
|
60
|
+
|
|
61
|
+
expect(result).toEqual({
|
|
62
|
+
ADUser: undefined,
|
|
63
|
+
SharepointUser: undefined,
|
|
64
|
+
IsValid: false,
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('getMeetingJoinInfo returns first meeting match when join id exists', async () => {
|
|
69
|
+
getMeetingManager.mockResolvedValue('manager-id');
|
|
70
|
+
apiGet.mockResolvedValue({
|
|
71
|
+
graphClientMessage: {
|
|
72
|
+
value: [{ id: 'online-meeting-id' }],
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const result = await getMeetingJoinInfo({
|
|
77
|
+
fields: {
|
|
78
|
+
JoinMeetingId: ' 123 45 ',
|
|
79
|
+
MeetingmanagerLookupId: 99,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
expect(getMeetingManager).toHaveBeenCalledWith(99);
|
|
84
|
+
expect(apiGet).toHaveBeenCalledWith(
|
|
85
|
+
"/users/manager-id/onlineMeetings?$filter=joinMeetingIdSettings/JoinMeetingId eq '12345'",
|
|
86
|
+
);
|
|
87
|
+
expect(result).toEqual({ id: 'online-meeting-id' });
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('getMeetingJoinInfo returns undefined when join id is missing', async () => {
|
|
91
|
+
const result = await getMeetingJoinInfo({
|
|
92
|
+
fields: {
|
|
93
|
+
JoinMeetingId: '',
|
|
94
|
+
MeetingmanagerLookupId: 99,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
expect(result).toBeUndefined();
|
|
99
|
+
expect(getMeetingManager).not.toHaveBeenCalled();
|
|
100
|
+
expect(apiGet).not.toHaveBeenCalled();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('getMeetingJoinInfo returns undefined when manager is missing', async () => {
|
|
104
|
+
getMeetingManager.mockResolvedValue(undefined);
|
|
105
|
+
|
|
106
|
+
const result = await getMeetingJoinInfo({
|
|
107
|
+
fields: {
|
|
108
|
+
JoinMeetingId: ' 123 45 ',
|
|
109
|
+
MeetingmanagerLookupId: 99,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
expect(result).toBeUndefined();
|
|
114
|
+
expect(apiGet).not.toHaveBeenCalled();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('getUser returns graph client message', async () => {
|
|
118
|
+
apiGet.mockResolvedValue({
|
|
119
|
+
graphClientMessage: { id: 'user-id', displayName: 'User' },
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const result = await getUser('user-id');
|
|
123
|
+
|
|
124
|
+
expect(apiGet).toHaveBeenCalledWith('/users/user-id');
|
|
125
|
+
expect(result).toEqual({ id: 'user-id', displayName: 'User' });
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('getUser returns undefined on api failure', async () => {
|
|
129
|
+
apiGet.mockRejectedValue(new Error('boom'));
|
|
130
|
+
|
|
131
|
+
const result = await getUser('user-id');
|
|
132
|
+
|
|
133
|
+
expect(result).toBeUndefined();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('sendEmail logs error when required fields are missing', async () => {
|
|
137
|
+
getConfiguration.mockResolvedValue({
|
|
138
|
+
FromEmailAddress: 'from@example.org',
|
|
139
|
+
DashboardEmailLoggingEnabled: 'false',
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
await sendEmail('', '<p>Body</p>', ['to@example.org']);
|
|
143
|
+
|
|
144
|
+
expect(logError).toHaveBeenCalledWith('Missing subject, body or recipients!', '', {
|
|
145
|
+
subject: '',
|
|
146
|
+
body: '<p>Body</p>',
|
|
147
|
+
recipients: ['to@example.org'],
|
|
148
|
+
});
|
|
149
|
+
expect(apiPost).not.toHaveBeenCalled();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('sendEmail sends mail and writes info log when email logging is enabled', async () => {
|
|
153
|
+
getConfiguration.mockResolvedValue({
|
|
154
|
+
FromEmailAddress: 'from@example.org',
|
|
155
|
+
DashboardEmailLoggingEnabled: 'true',
|
|
156
|
+
});
|
|
157
|
+
apiPost.mockResolvedValue(undefined);
|
|
158
|
+
logInfo.mockResolvedValue(undefined);
|
|
159
|
+
|
|
160
|
+
await sendEmail('Subject', '<p>Body</p>', ['to@example.org']);
|
|
161
|
+
|
|
162
|
+
expect(apiPost).toHaveBeenCalledWith('users/from@example.org/sendMail', {
|
|
163
|
+
message: {
|
|
164
|
+
subject: 'Subject',
|
|
165
|
+
body: {
|
|
166
|
+
contentType: 'HTML',
|
|
167
|
+
content: '<p>Body</p>',
|
|
168
|
+
},
|
|
169
|
+
toRecipients: [
|
|
170
|
+
{
|
|
171
|
+
emailAddress: {
|
|
172
|
+
address: 'to@example.org',
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
saveToSentItems: true,
|
|
178
|
+
});
|
|
179
|
+
expect(logInfo).toHaveBeenCalledWith(
|
|
180
|
+
'Mail sent during registration process',
|
|
181
|
+
'users/from@example.org/sendMail',
|
|
182
|
+
expect.objectContaining({
|
|
183
|
+
subject: 'Subject',
|
|
184
|
+
}),
|
|
185
|
+
'',
|
|
186
|
+
'to@example.org',
|
|
187
|
+
);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('sendEmail sends mail without info log when email logging is disabled', async () => {
|
|
191
|
+
getConfiguration.mockResolvedValue({
|
|
192
|
+
FromEmailAddress: 'from@example.org',
|
|
193
|
+
DashboardEmailLoggingEnabled: 'false',
|
|
194
|
+
});
|
|
195
|
+
apiPost.mockResolvedValue(undefined);
|
|
196
|
+
|
|
197
|
+
await sendEmail('Subject', '<p>Body</p>', ['first@example.org', 'second@example.org']);
|
|
198
|
+
|
|
199
|
+
expect(apiPost).toHaveBeenCalledWith('users/from@example.org/sendMail', {
|
|
200
|
+
message: expect.objectContaining({
|
|
201
|
+
toRecipients: [
|
|
202
|
+
{ emailAddress: { address: 'first@example.org' } },
|
|
203
|
+
{ emailAddress: { address: 'second@example.org' } },
|
|
204
|
+
],
|
|
205
|
+
}),
|
|
206
|
+
saveToSentItems: true,
|
|
207
|
+
});
|
|
208
|
+
expect(logInfo).not.toHaveBeenCalled();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test('sendEmail attaches ICS payload when attachment is provided', async () => {
|
|
212
|
+
getConfiguration.mockResolvedValue({
|
|
213
|
+
FromEmailAddress: 'from@example.org',
|
|
214
|
+
DashboardEmailLoggingEnabled: 'false',
|
|
215
|
+
});
|
|
216
|
+
apiPost.mockResolvedValue(undefined);
|
|
217
|
+
|
|
218
|
+
global.FileReader = class {
|
|
219
|
+
readAsDataURL() {
|
|
220
|
+
this.result = 'data:text/calendar;base64,ZmFrZS1pY3M=';
|
|
221
|
+
this.onloadend();
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
await sendEmail('Subject', '<p>Body</p>', ['to@example.org'], { name: 'event.ics' });
|
|
226
|
+
|
|
227
|
+
expect(apiPost).toHaveBeenCalledWith('users/from@example.org/sendMail', {
|
|
228
|
+
message: expect.objectContaining({
|
|
229
|
+
attachments: [
|
|
230
|
+
{
|
|
231
|
+
'@odata.type': '#microsoft.graph.fileAttachment',
|
|
232
|
+
name: 'event.ics',
|
|
233
|
+
contentType: 'text/calendar; charset=utf-8; method=REQUEST; name=event.ics',
|
|
234
|
+
contentBytes: 'ZmFrZS1pY3M=',
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
}),
|
|
238
|
+
saveToSentItems: true,
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test('getMe builds and caches profile for valid sharepoint user', async () => {
|
|
243
|
+
const { getMe } = loadProvider();
|
|
244
|
+
getConfiguration.mockResolvedValue({
|
|
245
|
+
AdminGroupId: 'admin-group',
|
|
246
|
+
NFPGroupId: 'nfp-group',
|
|
247
|
+
SelfSeviceHelpdeskPreferencesText: 'Pref text',
|
|
248
|
+
SelfSeviceHelpdeskPersonalDetailsText: 'Details text',
|
|
249
|
+
});
|
|
250
|
+
apiGet
|
|
251
|
+
.mockResolvedValueOnce({
|
|
252
|
+
graphClientMessage: {
|
|
253
|
+
displayName: 'John Doe',
|
|
254
|
+
mail: 'john@example.org',
|
|
255
|
+
country: 'RO',
|
|
256
|
+
},
|
|
257
|
+
})
|
|
258
|
+
.mockResolvedValueOnce({
|
|
259
|
+
graphClientMessage: {
|
|
260
|
+
value: [{ id: 'admin-group' }],
|
|
261
|
+
},
|
|
262
|
+
})
|
|
263
|
+
.mockResolvedValueOnce({
|
|
264
|
+
graphClientMessage: {
|
|
265
|
+
value: [{ givenName: 'John', surname: 'Doe' }],
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
getSPUserByMail.mockResolvedValue({
|
|
269
|
+
fields: {
|
|
270
|
+
Title: 'Mr',
|
|
271
|
+
Phone: '123',
|
|
272
|
+
Email: 'john@example.org',
|
|
273
|
+
Country: 'RO',
|
|
274
|
+
Membership: ['B', 'A'],
|
|
275
|
+
OtherMemberships: ['Y', 'X'],
|
|
276
|
+
Gender: 'M',
|
|
277
|
+
OrganisationLookupId: 11,
|
|
278
|
+
NFP: 'NFP',
|
|
279
|
+
SuggestedOrganisation: 'Org S',
|
|
280
|
+
Department: 'Dept',
|
|
281
|
+
JobTitle: 'Job',
|
|
282
|
+
PCP: 'PCP',
|
|
283
|
+
id: 1,
|
|
284
|
+
ADUserId: 77,
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
getOrganisationList.mockResolvedValue([{ content: 11, header: 'Org Header' }]);
|
|
288
|
+
|
|
289
|
+
const profile = await getMe();
|
|
290
|
+
const cached = await getMe();
|
|
291
|
+
|
|
292
|
+
expect(profile).toEqual(
|
|
293
|
+
expect.objectContaining({
|
|
294
|
+
displayName: 'John Doe',
|
|
295
|
+
mail: 'john@example.org',
|
|
296
|
+
country: 'RO',
|
|
297
|
+
isAdmin: true,
|
|
298
|
+
isNFP: false,
|
|
299
|
+
isGuest: false,
|
|
300
|
+
isEionetUser: true,
|
|
301
|
+
Organisation: 'Org Header',
|
|
302
|
+
Memberships: ['A', 'B'],
|
|
303
|
+
OtherMemberships: ['X', 'Y'],
|
|
304
|
+
}),
|
|
305
|
+
);
|
|
306
|
+
expect(cached).toBe(profile);
|
|
307
|
+
expect(getOrganisationList).toHaveBeenCalledWith('RO');
|
|
308
|
+
expect(apiGet).toHaveBeenCalledTimes(3);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test('getMe returns non-eionet profile when sharepoint user is missing', async () => {
|
|
312
|
+
const { getMe } = loadProvider();
|
|
313
|
+
getConfiguration.mockResolvedValue({
|
|
314
|
+
AdminGroupId: 'admin-group',
|
|
315
|
+
NFPGroupId: 'nfp-group',
|
|
316
|
+
});
|
|
317
|
+
apiGet
|
|
318
|
+
.mockResolvedValueOnce({
|
|
319
|
+
graphClientMessage: {
|
|
320
|
+
displayName: 'Guest User',
|
|
321
|
+
mail: 'guest@example.org',
|
|
322
|
+
country: 'DE',
|
|
323
|
+
},
|
|
324
|
+
})
|
|
325
|
+
.mockResolvedValueOnce({
|
|
326
|
+
graphClientMessage: {
|
|
327
|
+
value: [{ id: 'nfp-group' }],
|
|
328
|
+
},
|
|
329
|
+
})
|
|
330
|
+
.mockResolvedValueOnce({
|
|
331
|
+
graphClientMessage: {
|
|
332
|
+
value: [{ givenName: 'Guest', surname: 'User' }],
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
getSPUserByMail.mockResolvedValue(undefined);
|
|
336
|
+
|
|
337
|
+
const profile = await getMe();
|
|
338
|
+
|
|
339
|
+
expect(profile).toEqual(
|
|
340
|
+
expect.objectContaining({
|
|
341
|
+
displayName: 'Guest User',
|
|
342
|
+
mail: 'guest@example.org',
|
|
343
|
+
country: 'DE',
|
|
344
|
+
isEionetUser: false,
|
|
345
|
+
isAdmin: false,
|
|
346
|
+
isNFP: true,
|
|
347
|
+
isGuest: false,
|
|
348
|
+
}),
|
|
349
|
+
);
|
|
350
|
+
});
|
|
351
|
+
});
|