@forge/teamwork-graph 2.0.0-next.6 → 2.0.0-next.7

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 (55) hide show
  1. package/out/__test__/entity-operations.test.js +615 -10
  2. package/out/__test__/error-handling.test.js +38 -96
  3. package/out/__test__/graph-extended.test.js +12 -2
  4. package/out/__test__/group-operations.test.js +5 -4
  5. package/out/__test__/user-operations.test.js +16 -5
  6. package/out/__test__/validators.test.js +254 -218
  7. package/out/graph.d.ts +0 -3
  8. package/out/graph.d.ts.map +1 -1
  9. package/out/graph.js +121 -91
  10. package/out/index.d.ts +1 -2
  11. package/out/index.d.ts.map +1 -1
  12. package/out/index.js +1 -4
  13. package/out/types/entities/index.d.ts +11 -2
  14. package/out/types/entities/index.d.ts.map +1 -1
  15. package/out/types/entities/project.d.ts +40 -0
  16. package/out/types/entities/project.d.ts.map +1 -0
  17. package/out/types/entities/project.js +2 -0
  18. package/out/types/entities/pull-request.d.ts +44 -0
  19. package/out/types/entities/pull-request.d.ts.map +1 -0
  20. package/out/types/entities/pull-request.js +2 -0
  21. package/out/types/entities/remote-link.d.ts +33 -0
  22. package/out/types/entities/remote-link.d.ts.map +1 -0
  23. package/out/types/entities/remote-link.js +2 -0
  24. package/out/types/entities/repository.d.ts +14 -0
  25. package/out/types/entities/repository.d.ts.map +1 -0
  26. package/out/types/entities/repository.js +2 -0
  27. package/out/types/entities/software-service.d.ts +17 -0
  28. package/out/types/entities/software-service.d.ts.map +1 -0
  29. package/out/types/entities/software-service.js +2 -0
  30. package/out/types/entities/space.d.ts +21 -0
  31. package/out/types/entities/space.d.ts.map +1 -0
  32. package/out/types/entities/space.js +2 -0
  33. package/out/types/entities/video.d.ts +48 -0
  34. package/out/types/entities/video.d.ts.map +1 -0
  35. package/out/types/entities/video.js +2 -0
  36. package/out/types/entities/work-item.d.ts +44 -0
  37. package/out/types/entities/work-item.d.ts.map +1 -0
  38. package/out/types/entities/work-item.js +2 -0
  39. package/out/types/entities/worker.d.ts +23 -0
  40. package/out/types/entities/worker.d.ts.map +1 -0
  41. package/out/types/entities/worker.js +2 -0
  42. package/out/types/requests.d.ts +29 -8
  43. package/out/types/requests.d.ts.map +1 -1
  44. package/out/utils/error-handling.d.ts +4 -0
  45. package/out/utils/error-handling.d.ts.map +1 -0
  46. package/out/utils/error-handling.js +77 -0
  47. package/out/utils/errors.d.ts +21 -15
  48. package/out/utils/errors.d.ts.map +1 -1
  49. package/out/utils/errors.js +43 -15
  50. package/out/utils/validators.d.ts.map +1 -1
  51. package/out/utils/validators.js +18 -15
  52. package/package.json +1 -1
  53. package/out/error-handling.d.ts +0 -3
  54. package/out/error-handling.d.ts.map +0 -1
  55. package/out/error-handling.js +0 -36
@@ -14,10 +14,11 @@ describe('TeamWorkGraphClient - setEntities', () => {
14
14
  jest.clearAllMocks();
15
15
  });
16
16
  it('throws if entities array is empty', async () => {
17
- const req = { entities: [] };
18
- await expect(graphClient.setEntities(req)).rejects.toThrow('entities array cannot be empty');
17
+ await expect(graphClient.setEntities({
18
+ entities: []
19
+ })).rejects.toThrow('entities array cannot be empty');
19
20
  });
20
- it(`throws if more than ${validators_1.MAX_BULK_ENTITIES} entities`, async () => {
21
+ it('throws if more than 100 entities', async () => {
21
22
  const documentEntity = {
22
23
  schemaVersion: '1.0',
23
24
  id: 'my-document',
@@ -48,10 +49,10 @@ describe('TeamWorkGraphClient - setEntities', () => {
48
49
  }
49
50
  }
50
51
  };
51
- const req = {
52
- entities: Array(validators_1.MAX_BULK_ENTITIES + 1).fill(documentEntity)
53
- };
54
- await expect(graphClient.setEntities(req)).rejects.toThrow(`Bulk ingestion supports maximum ${validators_1.MAX_BULK_ENTITIES} entities`);
52
+ const manyEntities = Array(validators_1.MAX_BULK_ENTITIES + 1).fill(documentEntity);
53
+ await expect(graphClient.setEntities({
54
+ entities: manyEntities
55
+ })).rejects.toThrow(`Bulk ingestion supports maximum ${validators_1.MAX_BULK_ENTITIES} entities. Received ${validators_1.MAX_BULK_ENTITIES + 1}`);
55
56
  });
56
57
  it('posts to /api/v1/entities/bulk and returns response', async () => {
57
58
  const documentEntity = {
@@ -238,10 +239,10 @@ describe('TeamWorkGraphClient - getEntityByExternalId', () => {
238
239
  headers: {
239
240
  get: () => null
240
241
  },
241
- text: () => Promise.resolve(JSON.stringify({
242
+ json: () => Promise.resolve({
242
243
  code: 'ENTITY_NOT_FOUND',
243
244
  message: 'Entity not found'
244
- }))
245
+ })
245
246
  };
246
247
  mockFetch.mockResolvedValueOnce(errorResponse);
247
248
  const result = await graphClient.getEntityByExternalId({
@@ -250,7 +251,8 @@ describe('TeamWorkGraphClient - getEntityByExternalId', () => {
250
251
  });
251
252
  expect(result).toEqual({
252
253
  success: false,
253
- error: 'Entity not found'
254
+ error: 'Failed to get entity by external ID: Not Found - Entity not found',
255
+ originalError: expect.any(Error)
254
256
  });
255
257
  });
256
258
  it('should throw error when entityType is missing', async () => {
@@ -1019,4 +1021,607 @@ describe('TeamWorkGraphClient - deleteEntitiesByProperties', () => {
1019
1021
  expect(mockFetch).toHaveBeenCalledWith('/graph/connector/api/v1/entities/bulk', expect.objectContaining({ method: 'POST' }));
1020
1022
  expect(result).toEqual(expected);
1021
1023
  });
1024
+ it('posts project entities to /api/v1/entities/bulk and returns response', async () => {
1025
+ const projectEntity = {
1026
+ schemaVersion: '2.0',
1027
+ id: 'project-1',
1028
+ updateSequenceNumber: 1,
1029
+ displayName: 'E-commerce Platform Redesign',
1030
+ description: 'Complete redesign of the e-commerce platform with modern UI/UX and improved performance',
1031
+ url: 'https://jira.example.com/projects/ECOM-123',
1032
+ createdAt: '2024-07-09T14:27:37.000Z',
1033
+ createdBy: {
1034
+ externalId: 'creator-user-123',
1035
+ userName: 'jdoe',
1036
+ displayName: 'John Doe',
1037
+ emails: [
1038
+ {
1039
+ value: 'john.doe@example.com',
1040
+ primary: true
1041
+ }
1042
+ ]
1043
+ },
1044
+ lastUpdatedAt: '2024-07-09T14:27:37.000Z',
1045
+ permissions: {
1046
+ accessControls: [
1047
+ {
1048
+ principals: [
1049
+ {
1050
+ type: 'EVERYONE'
1051
+ }
1052
+ ]
1053
+ }
1054
+ ]
1055
+ },
1056
+ 'atlassian:project': {
1057
+ key: 'ECOM-123',
1058
+ dueDate: '2024-12-31T23:59:59.000Z',
1059
+ priority: 'High',
1060
+ assignee: {
1061
+ accountId: 'project-manager-123',
1062
+ ari: 'ari:cloud:identity::user/project-manager-123',
1063
+ externalId: 'project-manager-123',
1064
+ name: 'Sarah Johnson',
1065
+ email: 'sarah.johnson@example.com',
1066
+ userName: 'sjohnson',
1067
+ avatar: 'https://avatar.example.com/sarah.jpg'
1068
+ },
1069
+ status: 'In Progress',
1070
+ attachments: [
1071
+ {
1072
+ url: 'https://files.example.com/project-spec.pdf',
1073
+ thumbnailUrl: 'https://files.example.com/thumbnails/project-spec.jpg',
1074
+ title: 'Project Specification Document',
1075
+ mimeType: 'application/pdf',
1076
+ byteSize: 2048000
1077
+ },
1078
+ {
1079
+ url: 'https://files.example.com/wireframes.figma',
1080
+ thumbnailUrl: 'https://files.example.com/thumbnails/wireframes.jpg',
1081
+ title: 'UI Wireframes',
1082
+ mimeType: 'application/figma',
1083
+ byteSize: 1024000
1084
+ }
1085
+ ],
1086
+ labels: ['frontend', 'redesign', 'high-priority'],
1087
+ environment: 'production',
1088
+ resolution: 'In Progress',
1089
+ votesCount: 12,
1090
+ watchersCount: 8
1091
+ }
1092
+ };
1093
+ const req = { entities: [projectEntity] };
1094
+ const expected = { success: true, results: [{ entityId: 'project-1', success: true }] };
1095
+ mockFetch.mockResolvedValueOnce({
1096
+ ok: true,
1097
+ json: () => Promise.resolve(expected)
1098
+ });
1099
+ const result = await graphClient.setEntities(req);
1100
+ expect(mockFetch).toHaveBeenCalledWith('/graph/connector/api/v1/entities/bulk', expect.objectContaining({ method: 'POST' }));
1101
+ expect(result).toEqual(expected);
1102
+ });
1103
+ it('posts pull request entities to /api/v1/entities/bulk and returns response', async () => {
1104
+ const pullRequestEntity = {
1105
+ schemaVersion: '2.0',
1106
+ id: 'pr-1',
1107
+ updateSequenceNumber: 1,
1108
+ displayName: 'Add user authentication feature',
1109
+ description: 'Implement OAuth2 authentication system with JWT tokens for enhanced security',
1110
+ url: 'https://github.com/company/repo/pull/42',
1111
+ createdAt: '2024-07-09T14:27:37.000Z',
1112
+ containerKey: {
1113
+ type: 'atlassian:repository',
1114
+ value: {
1115
+ entityId: 'repo-company-main-123'
1116
+ }
1117
+ },
1118
+ lastUpdatedAt: '2024-07-09T14:27:37.000Z',
1119
+ permissions: {
1120
+ accessControls: [
1121
+ {
1122
+ principals: [
1123
+ {
1124
+ type: 'EVERYONE'
1125
+ }
1126
+ ]
1127
+ }
1128
+ ]
1129
+ },
1130
+ 'atlassian:pull-request': {
1131
+ title: 'Add user authentication feature',
1132
+ displayId: 'PR-42',
1133
+ status: 'OPEN',
1134
+ author: {
1135
+ accountId: 'author-dev-456',
1136
+ externalId: 'author-dev-456',
1137
+ name: 'John Smith',
1138
+ userName: 'jsmith',
1139
+ email: 'john.smith@example.com',
1140
+ avatar: 'https://avatar.example.com/john.jpg',
1141
+ url: 'https://github.com/jsmith'
1142
+ },
1143
+ commentCount: 8,
1144
+ sourceBranch: 'feature/oauth2-auth',
1145
+ sourceBranchUrl: 'https://github.com/company/repo/tree/feature/oauth2-auth',
1146
+ destinationBranch: 'main',
1147
+ destinationBranchUrl: 'https://github.com/company/repo/tree/main',
1148
+ reviewers: [
1149
+ {
1150
+ accountId: 'reviewer-senior-789',
1151
+ id: 'reviewer-senior-789',
1152
+ email: 'alice.johnson@example.com',
1153
+ approvalStatus: 'approved',
1154
+ name: 'Alice Johnson',
1155
+ avatar: 'https://avatar.example.com/alice.jpg',
1156
+ url: 'https://github.com/alicejohnson',
1157
+ ari: 'ari:cloud:identity::user/reviewer-senior-789'
1158
+ },
1159
+ {
1160
+ accountId: 'reviewer-lead-321',
1161
+ id: 'reviewer-lead-321',
1162
+ email: 'bob.wilson@example.com',
1163
+ approvalStatus: 'unapproved',
1164
+ name: 'Bob Wilson',
1165
+ avatar: 'https://avatar.example.com/bob.jpg',
1166
+ url: 'https://github.com/bobwilson',
1167
+ ari: 'ari:cloud:identity::user/reviewer-lead-321'
1168
+ }
1169
+ ],
1170
+ taskCount: 3
1171
+ }
1172
+ };
1173
+ const req = { entities: [pullRequestEntity] };
1174
+ const expected = { success: true, results: [{ entityId: 'pr-1', success: true }] };
1175
+ mockFetch.mockResolvedValueOnce({
1176
+ ok: true,
1177
+ json: () => Promise.resolve(expected)
1178
+ });
1179
+ const result = await graphClient.setEntities(req);
1180
+ expect(mockFetch).toHaveBeenCalledWith('/graph/connector/api/v1/entities/bulk', expect.objectContaining({ method: 'POST' }));
1181
+ expect(result).toEqual(expected);
1182
+ });
1183
+ it('posts remote link entities to /api/v1/entities/bulk and returns response', async () => {
1184
+ const remoteLinkEntity = {
1185
+ schemaVersion: '2.0',
1186
+ id: 'remote-link-1',
1187
+ updateSequenceNumber: 1,
1188
+ displayName: 'JIRA Issue Link - Fix user authentication bug',
1189
+ url: 'https://company.atlassian.net/browse/AUTH-123',
1190
+ lastUpdatedAt: '2024-07-09T14:27:37.000Z',
1191
+ permissions: {
1192
+ accessControls: [
1193
+ {
1194
+ principals: [
1195
+ {
1196
+ type: 'EVERYONE'
1197
+ }
1198
+ ]
1199
+ }
1200
+ ]
1201
+ },
1202
+ 'atlassian:remote-link': {
1203
+ type: 'issue',
1204
+ status: {
1205
+ appearance: 'in_progress',
1206
+ label: 'In Progress'
1207
+ },
1208
+ actionIds: ['view', 'edit', 'comment'],
1209
+ attributeMap: {
1210
+ priority: 'high',
1211
+ storyPoints: 5,
1212
+ sprint: 'Sprint 23',
1213
+ component: 'Authentication Service'
1214
+ },
1215
+ author: {
1216
+ accountId: 'author-dev-789',
1217
+ externalId: 'author-dev-789',
1218
+ name: 'Sarah Johnson',
1219
+ userName: 'sjohnson',
1220
+ email: 'sarah.johnson@example.com',
1221
+ avatar: 'https://avatar.example.com/sarah.jpg',
1222
+ url: 'https://company.atlassian.net/people/sarah.johnson'
1223
+ },
1224
+ category: 'bug',
1225
+ assignee: {
1226
+ accountId: 'assignee-dev-456',
1227
+ externalId: 'assignee-dev-456',
1228
+ name: 'Michael Chen',
1229
+ userName: 'mchen',
1230
+ email: 'michael.chen@example.com',
1231
+ avatar: 'https://avatar.example.com/michael.jpg',
1232
+ url: 'https://company.atlassian.net/people/michael.chen'
1233
+ },
1234
+ fullNounThirdPartyAri: 'ari:cloud:jira:company-site:issue/AUTH-123'
1235
+ }
1236
+ };
1237
+ const req = { entities: [remoteLinkEntity] };
1238
+ const expected = { success: true, results: [{ entityId: 'remote-link-1', success: true }] };
1239
+ mockFetch.mockResolvedValueOnce({
1240
+ ok: true,
1241
+ json: () => Promise.resolve(expected)
1242
+ });
1243
+ const result = await graphClient.setEntities(req);
1244
+ expect(mockFetch).toHaveBeenCalledWith('/graph/connector/api/v1/entities/bulk', expect.objectContaining({ method: 'POST' }));
1245
+ expect(result).toEqual(expected);
1246
+ });
1247
+ it('posts repository entities to /api/v1/entities/bulk and returns response', async () => {
1248
+ const repositoryEntity = {
1249
+ schemaVersion: '2.0',
1250
+ id: 'repository-1',
1251
+ updateSequenceNumber: 1,
1252
+ displayName: 'E-commerce Platform Backend',
1253
+ url: 'https://github.com/company/ecommerce-backend',
1254
+ createdAt: '2024-07-09T14:27:37.000Z',
1255
+ lastUpdatedAt: '2024-07-09T14:27:37.000Z',
1256
+ permissions: {
1257
+ accessControls: [
1258
+ {
1259
+ principals: [
1260
+ {
1261
+ type: 'EVERYONE'
1262
+ }
1263
+ ]
1264
+ }
1265
+ ]
1266
+ },
1267
+ 'atlassian:repository': {
1268
+ name: 'ecommerce-backend',
1269
+ forkOf: 'https://github.com/upstream/ecommerce-platform',
1270
+ avatar: 'https://avatars.githubusercontent.com/u/company?v=4',
1271
+ avatarDescription: 'Company logo representing the ecommerce backend repository'
1272
+ }
1273
+ };
1274
+ const req = { entities: [repositoryEntity] };
1275
+ const expected = { success: true, results: [{ entityId: 'repository-1', success: true }] };
1276
+ mockFetch.mockResolvedValueOnce({
1277
+ ok: true,
1278
+ json: () => Promise.resolve(expected)
1279
+ });
1280
+ const result = await graphClient.setEntities(req);
1281
+ expect(mockFetch).toHaveBeenCalledWith('/graph/connector/api/v1/entities/bulk', expect.objectContaining({ method: 'POST' }));
1282
+ expect(result).toEqual(expected);
1283
+ });
1284
+ it('posts software service entities to /api/v1/entities/bulk and returns response', async () => {
1285
+ const softwareServiceEntity = {
1286
+ schemaVersion: '2.0',
1287
+ id: 'software-service-1',
1288
+ updateSequenceNumber: 1,
1289
+ displayName: 'Payment Processing Service',
1290
+ url: 'https://monitoring.example.com/services/payment-processor',
1291
+ createdAt: '2024-07-09T14:27:37.000Z',
1292
+ lastUpdatedAt: '2024-07-09T14:27:37.000Z',
1293
+ permissions: {
1294
+ accessControls: [
1295
+ {
1296
+ principals: [
1297
+ {
1298
+ type: 'EVERYONE'
1299
+ }
1300
+ ]
1301
+ }
1302
+ ]
1303
+ },
1304
+ 'atlassian:software-service': {
1305
+ description: 'Microservice responsible for processing all payment transactions with secure payment gateway integration',
1306
+ associationsMetadata: {
1307
+ ownership: 'payments-team',
1308
+ alertingChannel: '#payments-alerts',
1309
+ documentationUrl: 'https://docs.example.com/payment-service',
1310
+ repository: 'https://github.com/company/payment-processor',
1311
+ dashboardUrl: 'https://grafana.example.com/payment-service'
1312
+ },
1313
+ namespace: 'payment-system',
1314
+ environment: 'production',
1315
+ tags: ['payment', 'critical', 'pci-compliant', 'microservice', 'high-availability'],
1316
+ tier: 'tier-1',
1317
+ serviceType: 'REST API'
1318
+ }
1319
+ };
1320
+ const req = { entities: [softwareServiceEntity] };
1321
+ const expected = { success: true, results: [{ entityId: 'software-service-1', success: true }] };
1322
+ mockFetch.mockResolvedValueOnce({
1323
+ ok: true,
1324
+ json: () => Promise.resolve(expected)
1325
+ });
1326
+ const result = await graphClient.setEntities(req);
1327
+ expect(mockFetch).toHaveBeenCalledWith('/graph/connector/api/v1/entities/bulk', expect.objectContaining({ method: 'POST' }));
1328
+ expect(result).toEqual(expected);
1329
+ });
1330
+ it('posts space entities to /api/v1/entities/bulk and returns response', async () => {
1331
+ const spaceEntity = {
1332
+ schemaVersion: '2.0',
1333
+ id: 'space-1',
1334
+ updateSequenceNumber: 1,
1335
+ displayName: 'Engineering Team Documentation',
1336
+ url: 'https://company.atlassian.net/wiki/spaces/ENGDOC',
1337
+ createdAt: '2024-07-09T14:27:37.000Z',
1338
+ lastUpdatedAt: '2024-07-09T14:27:37.000Z',
1339
+ permissions: {
1340
+ accessControls: [
1341
+ {
1342
+ principals: [
1343
+ {
1344
+ type: 'EVERYONE'
1345
+ }
1346
+ ]
1347
+ }
1348
+ ]
1349
+ },
1350
+ 'atlassian:space': {
1351
+ key: 'ENGDOC',
1352
+ spaceType: 'global',
1353
+ subtype: 'documentation',
1354
+ icon: {
1355
+ url: 'https://company.atlassian.net/wiki/aa-avatar/5f7e8c123456789',
1356
+ width: 64,
1357
+ height: 64,
1358
+ isDefault: false
1359
+ },
1360
+ labels: ['engineering', 'documentation', 'team-resources', 'best-practices', 'onboarding']
1361
+ }
1362
+ };
1363
+ const req = { entities: [spaceEntity] };
1364
+ const expected = { success: true, results: [{ entityId: 'space-1', success: true }] };
1365
+ mockFetch.mockResolvedValueOnce({
1366
+ ok: true,
1367
+ json: () => Promise.resolve(expected)
1368
+ });
1369
+ const result = await graphClient.setEntities(req);
1370
+ expect(mockFetch).toHaveBeenCalledWith('/graph/connector/api/v1/entities/bulk', expect.objectContaining({ method: 'POST' }));
1371
+ expect(result).toEqual(expected);
1372
+ });
1373
+ it('posts video entities to /api/v1/entities/bulk and returns response', async () => {
1374
+ const videoEntity = {
1375
+ schemaVersion: '2.0',
1376
+ id: 'video-1',
1377
+ updateSequenceNumber: 1,
1378
+ displayName: 'Introduction to React Hooks - Team Training',
1379
+ url: 'https://company.video.com/watch/react-hooks-intro',
1380
+ createdAt: '2024-07-09T14:27:37.000Z',
1381
+ lastUpdatedAt: '2024-07-09T14:27:37.000Z',
1382
+ permissions: {
1383
+ accessControls: [
1384
+ {
1385
+ principals: [
1386
+ {
1387
+ type: 'EVERYONE'
1388
+ }
1389
+ ]
1390
+ }
1391
+ ]
1392
+ },
1393
+ 'atlassian:video': {
1394
+ thumbnailUrl: 'https://company.video.com/thumbnails/react-hooks-intro.jpg',
1395
+ embedUrl: 'https://company.video.com/embed/react-hooks-intro',
1396
+ durationInSeconds: 1800,
1397
+ width: 1920,
1398
+ height: 1080,
1399
+ commentCount: 15,
1400
+ textTracks: [
1401
+ {
1402
+ name: 'English Subtitles',
1403
+ locale: 'en-US',
1404
+ cues: [
1405
+ {
1406
+ id: 'cue-1',
1407
+ startTimeInSeconds: 0.0,
1408
+ endTimeInSeconds: 5.5,
1409
+ text: 'Welcome to our React Hooks training session.'
1410
+ },
1411
+ {
1412
+ id: 'cue-2',
1413
+ startTimeInSeconds: 5.5,
1414
+ endTimeInSeconds: 12.0,
1415
+ text: 'Today we will cover useState, useEffect, and custom hooks.'
1416
+ }
1417
+ ]
1418
+ },
1419
+ {
1420
+ name: 'Spanish Subtitles',
1421
+ locale: 'es-ES',
1422
+ cues: [
1423
+ {
1424
+ id: 'cue-es-1',
1425
+ startTimeInSeconds: 0.0,
1426
+ endTimeInSeconds: 5.5,
1427
+ text: 'Bienvenidos a nuestra sesión de entrenamiento de React Hooks.'
1428
+ }
1429
+ ]
1430
+ }
1431
+ ],
1432
+ chapters: [
1433
+ {
1434
+ startTimeInSeconds: 0,
1435
+ title: 'Introduction'
1436
+ },
1437
+ {
1438
+ startTimeInSeconds: 300,
1439
+ title: 'useState Hook'
1440
+ },
1441
+ {
1442
+ startTimeInSeconds: 900,
1443
+ title: 'useEffect Hook'
1444
+ },
1445
+ {
1446
+ startTimeInSeconds: 1500,
1447
+ title: 'Custom Hooks'
1448
+ }
1449
+ ],
1450
+ contributors: [
1451
+ {
1452
+ user: {
1453
+ accountId: 'instructor-123',
1454
+ externalId: 'instructor-123',
1455
+ name: 'Sarah Johnson',
1456
+ userName: 'sjohnson',
1457
+ email: 'sarah.johnson@example.com',
1458
+ avatar: 'https://avatar.example.com/sarah.jpg',
1459
+ url: 'https://company.com/people/sarah.johnson'
1460
+ },
1461
+ interactionCount: 25
1462
+ },
1463
+ {
1464
+ user: {
1465
+ accountId: 'participant-456',
1466
+ externalId: 'participant-456',
1467
+ name: 'Mike Chen',
1468
+ userName: 'mchen',
1469
+ email: 'mike.chen@example.com',
1470
+ avatar: 'https://avatar.example.com/mike.jpg',
1471
+ url: 'https://company.com/people/mike.chen'
1472
+ },
1473
+ interactionCount: 8
1474
+ }
1475
+ ]
1476
+ }
1477
+ };
1478
+ const req = { entities: [videoEntity] };
1479
+ const expected = { success: true, results: [{ entityId: 'video-1', success: true }] };
1480
+ mockFetch.mockResolvedValueOnce({
1481
+ ok: true,
1482
+ json: () => Promise.resolve(expected)
1483
+ });
1484
+ const result = await graphClient.setEntities(req);
1485
+ expect(mockFetch).toHaveBeenCalledWith('/graph/connector/api/v1/entities/bulk', expect.objectContaining({ method: 'POST' }));
1486
+ expect(result).toEqual(expected);
1487
+ });
1488
+ it('posts work item entities to /api/v1/entities/bulk and returns response', async () => {
1489
+ const workItemEntity = {
1490
+ schemaVersion: '2.0',
1491
+ id: 'work-item-1',
1492
+ updateSequenceNumber: 1,
1493
+ displayName: 'Implement user authentication API endpoints',
1494
+ description: 'Develop secure REST API endpoints for user authentication including login, logout, password reset, and token refresh functionality with JWT implementation',
1495
+ url: 'https://company.jira.com/browse/AUTH-456',
1496
+ createdAt: '2024-07-09T14:27:37.000Z',
1497
+ containerKey: {
1498
+ type: 'atlassian:space',
1499
+ value: {
1500
+ entityId: 'space-engineering-team-docs'
1501
+ }
1502
+ },
1503
+ lastUpdatedAt: '2024-07-09T14:27:37.000Z',
1504
+ permissions: {
1505
+ accessControls: [
1506
+ {
1507
+ principals: [
1508
+ {
1509
+ type: 'EVERYONE'
1510
+ }
1511
+ ]
1512
+ }
1513
+ ]
1514
+ },
1515
+ 'atlassian:work-item': {
1516
+ dueDate: '2024-08-15T17:00:00.000Z',
1517
+ assignee: {
1518
+ accountId: 'dev-lead-789',
1519
+ externalId: 'dev-lead-789',
1520
+ name: 'Alex Rodriguez',
1521
+ userName: 'arodriguez',
1522
+ email: 'alex.rodriguez@example.com',
1523
+ avatar: 'https://avatar.example.com/alex.jpg',
1524
+ url: 'https://company.com/people/alex.rodriguez'
1525
+ },
1526
+ workItemProject: {
1527
+ id: 'auth-service-project',
1528
+ name: 'Authentication Service Upgrade'
1529
+ },
1530
+ collaborators: [
1531
+ {
1532
+ accountId: 'backend-dev-123',
1533
+ externalId: 'backend-dev-123',
1534
+ name: 'Sarah Chen',
1535
+ userName: 'schen',
1536
+ email: 'sarah.chen@example.com',
1537
+ avatar: 'https://avatar.example.com/sarah.jpg',
1538
+ url: 'https://company.com/people/sarah.chen'
1539
+ },
1540
+ {
1541
+ accountId: 'security-expert-456',
1542
+ externalId: 'security-expert-456',
1543
+ name: 'Michael Thompson',
1544
+ userName: 'mthompson',
1545
+ email: 'michael.thompson@example.com',
1546
+ avatar: 'https://avatar.example.com/michael.jpg',
1547
+ url: 'https://company.com/people/michael.thompson'
1548
+ }
1549
+ ],
1550
+ exceedsMaxCollaborators: false,
1551
+ status: 'In Progress',
1552
+ subtype: 'Development Task',
1553
+ attachments: [
1554
+ {
1555
+ url: 'https://files.example.com/auth-api-spec.pdf',
1556
+ thumbnailUrl: 'https://files.example.com/thumbnails/auth-api-spec.jpg',
1557
+ title: 'Authentication API Specification',
1558
+ mimeType: 'application/pdf',
1559
+ byteSize: 1024000
1560
+ },
1561
+ {
1562
+ url: 'https://files.example.com/security-requirements.docx',
1563
+ thumbnailUrl: 'https://files.example.com/thumbnails/security-requirements.jpg',
1564
+ title: 'Security Requirements Document',
1565
+ mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
1566
+ byteSize: 512000
1567
+ }
1568
+ ],
1569
+ team: 'Backend Infrastructure Team'
1570
+ }
1571
+ };
1572
+ const req = { entities: [workItemEntity] };
1573
+ const expected = { success: true, results: [{ entityId: 'work-item-1', success: true }] };
1574
+ mockFetch.mockResolvedValueOnce({
1575
+ ok: true,
1576
+ json: () => Promise.resolve(expected)
1577
+ });
1578
+ const result = await graphClient.setEntities(req);
1579
+ expect(mockFetch).toHaveBeenCalledWith('/graph/connector/api/v1/entities/bulk', expect.objectContaining({ method: 'POST' }));
1580
+ expect(result).toEqual(expected);
1581
+ });
1582
+ it('posts worker entities to /api/v1/entities/bulk and returns response', async () => {
1583
+ const workerEntity = {
1584
+ schemaVersion: '2.0',
1585
+ id: 'worker-1',
1586
+ updateSequenceNumber: 1,
1587
+ displayName: 'Sarah Johnson - Senior Software Engineer',
1588
+ url: 'https://hr.example.com/employees/sarah-johnson',
1589
+ createdAt: '2024-07-09T14:27:37.000Z',
1590
+ lastUpdatedAt: '2024-07-09T14:27:37.000Z',
1591
+ permissions: {
1592
+ accessControls: [
1593
+ {
1594
+ principals: [
1595
+ {
1596
+ type: 'EVERYONE'
1597
+ }
1598
+ ]
1599
+ }
1600
+ ]
1601
+ },
1602
+ 'atlassian:worker': {
1603
+ hiredAt: '2022-03-15T09:00:00.000Z',
1604
+ workerUser: {
1605
+ accountId: 'worker-sarah-123',
1606
+ externalId: 'emp-sarah-456',
1607
+ name: 'Sarah Johnson',
1608
+ userName: 'sjohnson',
1609
+ email: 'sarah.johnson@example.com',
1610
+ avatar: 'https://avatar.example.com/sarah.jpg',
1611
+ url: 'https://company.com/people/sarah.johnson',
1612
+ ari: 'ari:cloud:identity::user/worker-sarah-123'
1613
+ },
1614
+ customAndSensitiveData: 'Employee ID: EMP001234, Department: Engineering, Salary Grade: L5, Manager: Alex Rodriguez, Office Location: San Francisco HQ'
1615
+ }
1616
+ };
1617
+ const req = { entities: [workerEntity] };
1618
+ const expected = { success: true, results: [{ entityId: 'worker-1', success: true }] };
1619
+ mockFetch.mockResolvedValueOnce({
1620
+ ok: true,
1621
+ json: () => Promise.resolve(expected)
1622
+ });
1623
+ const result = await graphClient.setEntities(req);
1624
+ expect(mockFetch).toHaveBeenCalledWith('/graph/connector/api/v1/entities/bulk', expect.objectContaining({ method: 'POST' }));
1625
+ expect(result).toEqual(expected);
1626
+ });
1022
1627
  });