@forge/teamwork-graph 1.2.0-next.5 → 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 (82) hide show
  1. package/out/__test__/entity-operations.test.js +1155 -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/build.d.ts +33 -0
  14. package/out/types/entities/build.d.ts.map +1 -0
  15. package/out/types/entities/build.js +2 -0
  16. package/out/types/entities/calendar-event.d.ts +45 -0
  17. package/out/types/entities/calendar-event.d.ts.map +1 -0
  18. package/out/types/entities/calendar-event.js +2 -0
  19. package/out/types/entities/comment.d.ts +18 -0
  20. package/out/types/entities/comment.d.ts.map +1 -0
  21. package/out/types/entities/comment.js +2 -0
  22. package/out/types/entities/conversation.d.ts +28 -0
  23. package/out/types/entities/conversation.d.ts.map +1 -0
  24. package/out/types/entities/conversation.js +2 -0
  25. package/out/types/entities/customer-org.d.ts +36 -0
  26. package/out/types/entities/customer-org.d.ts.map +1 -0
  27. package/out/types/entities/customer-org.js +2 -0
  28. package/out/types/entities/deal.d.ts +37 -0
  29. package/out/types/entities/deal.d.ts.map +1 -0
  30. package/out/types/entities/deal.js +2 -0
  31. package/out/types/entities/deployment.d.ts +43 -0
  32. package/out/types/entities/deployment.d.ts.map +1 -0
  33. package/out/types/entities/deployment.js +2 -0
  34. package/out/types/entities/design.d.ts +15 -0
  35. package/out/types/entities/design.d.ts.map +1 -0
  36. package/out/types/entities/design.js +2 -0
  37. package/out/types/entities/index.d.ts +20 -2
  38. package/out/types/entities/index.d.ts.map +1 -1
  39. package/out/types/entities/position.d.ts +24 -0
  40. package/out/types/entities/position.d.ts.map +1 -0
  41. package/out/types/entities/position.js +2 -0
  42. package/out/types/entities/project.d.ts +40 -0
  43. package/out/types/entities/project.d.ts.map +1 -0
  44. package/out/types/entities/project.js +2 -0
  45. package/out/types/entities/pull-request.d.ts +44 -0
  46. package/out/types/entities/pull-request.d.ts.map +1 -0
  47. package/out/types/entities/pull-request.js +2 -0
  48. package/out/types/entities/remote-link.d.ts +33 -0
  49. package/out/types/entities/remote-link.d.ts.map +1 -0
  50. package/out/types/entities/remote-link.js +2 -0
  51. package/out/types/entities/repository.d.ts +14 -0
  52. package/out/types/entities/repository.d.ts.map +1 -0
  53. package/out/types/entities/repository.js +2 -0
  54. package/out/types/entities/software-service.d.ts +17 -0
  55. package/out/types/entities/software-service.d.ts.map +1 -0
  56. package/out/types/entities/software-service.js +2 -0
  57. package/out/types/entities/space.d.ts +21 -0
  58. package/out/types/entities/space.d.ts.map +1 -0
  59. package/out/types/entities/space.js +2 -0
  60. package/out/types/entities/video.d.ts +48 -0
  61. package/out/types/entities/video.d.ts.map +1 -0
  62. package/out/types/entities/video.js +2 -0
  63. package/out/types/entities/work-item.d.ts +44 -0
  64. package/out/types/entities/work-item.d.ts.map +1 -0
  65. package/out/types/entities/work-item.js +2 -0
  66. package/out/types/entities/worker.d.ts +23 -0
  67. package/out/types/entities/worker.d.ts.map +1 -0
  68. package/out/types/entities/worker.js +2 -0
  69. package/out/types/requests.d.ts +29 -8
  70. package/out/types/requests.d.ts.map +1 -1
  71. package/out/utils/error-handling.d.ts +4 -0
  72. package/out/utils/error-handling.d.ts.map +1 -0
  73. package/out/utils/error-handling.js +77 -0
  74. package/out/utils/errors.d.ts +21 -15
  75. package/out/utils/errors.d.ts.map +1 -1
  76. package/out/utils/errors.js +43 -15
  77. package/out/utils/validators.d.ts.map +1 -1
  78. package/out/utils/validators.js +18 -15
  79. package/package.json +1 -1
  80. package/out/error-handling.d.ts +0 -3
  81. package/out/error-handling.d.ts.map +0 -1
  82. 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 () => {
@@ -479,4 +481,1147 @@ describe('TeamWorkGraphClient - deleteEntitiesByProperties', () => {
479
481
  expect(mockFetch).toHaveBeenCalledWith('/graph/connector/api/v1/entities/bulk', expect.objectContaining({ method: 'POST' }));
480
482
  expect(result).toEqual(expected);
481
483
  });
484
+ it('posts build entities to /api/v1/entities/bulk and returns response', async () => {
485
+ const buildEntity = {
486
+ schemaVersion: '2.0',
487
+ id: 'build-1',
488
+ updateSequenceNumber: 1,
489
+ displayName: 'Build #42',
490
+ url: 'https://ci.example.com/builds/42',
491
+ createdAt: '2024-07-09T14:27:37.000Z',
492
+ lastUpdatedAt: '2024-07-09T14:27:37.000Z',
493
+ permissions: {
494
+ accessControls: [
495
+ {
496
+ principals: [
497
+ {
498
+ type: 'EVERYONE'
499
+ }
500
+ ]
501
+ }
502
+ ]
503
+ },
504
+ 'atlassian:build': {
505
+ pipelineId: 'pipeline-123',
506
+ buildNumber: 42,
507
+ label: 'Release Build',
508
+ state: 'SUCCESS',
509
+ duration: 300,
510
+ testInfo: {
511
+ totalNumber: 150,
512
+ numberPassed: 145,
513
+ numberFailed: 3,
514
+ numberSkipped: 2
515
+ },
516
+ references: [
517
+ {
518
+ commit: {
519
+ id: 'abc123def456',
520
+ repositoryUri: 'https://github.com/org/repo'
521
+ },
522
+ ref: {
523
+ name: 'main',
524
+ uri: 'https://github.com/org/repo/tree/main'
525
+ }
526
+ }
527
+ ]
528
+ }
529
+ };
530
+ const req = { entities: [buildEntity] };
531
+ const expected = { success: true, results: [{ entityId: 'build-1', success: true }] };
532
+ mockFetch.mockResolvedValueOnce({
533
+ ok: true,
534
+ json: () => Promise.resolve(expected)
535
+ });
536
+ const result = await graphClient.setEntities(req);
537
+ expect(mockFetch).toHaveBeenCalledWith('/graph/connector/api/v1/entities/bulk', expect.objectContaining({ method: 'POST' }));
538
+ expect(result).toEqual(expected);
539
+ });
540
+ it('posts calendar event entities to /api/v1/entities/bulk and returns response', async () => {
541
+ const calendarEventEntity = {
542
+ schemaVersion: '2.0',
543
+ id: 'calendar-event-1',
544
+ updateSequenceNumber: 1,
545
+ displayName: 'Sprint Planning Meeting',
546
+ description: 'Weekly sprint planning session for the development team',
547
+ url: 'https://calendar.google.com/event/abc123',
548
+ createdAt: '2024-07-09T14:27:37.000Z',
549
+ createdBy: {},
550
+ lastUpdatedAt: '2024-07-09T14:27:37.000Z',
551
+ permissions: {
552
+ accessControls: [
553
+ {
554
+ principals: [
555
+ {
556
+ type: 'EVERYONE'
557
+ }
558
+ ]
559
+ }
560
+ ]
561
+ },
562
+ 'atlassian:calendar-event': {
563
+ eventStartTime: '2024-07-10T09:00:00.000Z',
564
+ eventEndTime: '2024-07-10T10:00:00.000Z',
565
+ eventType: 'meeting',
566
+ attendees: [
567
+ {
568
+ user: 'user-123',
569
+ isOptional: false,
570
+ rsvpStatus: 'accepted'
571
+ },
572
+ {
573
+ user: 'user-456',
574
+ isOptional: true,
575
+ rsvpStatus: 'tentatively_accepted'
576
+ }
577
+ ],
578
+ location: {
579
+ name: 'Conference Room A',
580
+ address: '123 Main St, Building 1, Floor 2',
581
+ url: 'https://maps.google.com/conference-room-a',
582
+ coordinates: '40.7128,-74.0060'
583
+ },
584
+ videoMeetingUrl: 'https://meet.google.com/abc-defg-hij',
585
+ videoMeetingProvider: 'Google Meet',
586
+ isAllDayEvent: false,
587
+ attendeeCount: 2,
588
+ exceedsMaxAttendees: false,
589
+ isRecurringEvent: true,
590
+ recurringEventId: 'recurring-123',
591
+ attachments: [
592
+ {
593
+ url: 'https://example.com/agenda.pdf',
594
+ title: 'Meeting Agenda',
595
+ mimeType: 'application/pdf',
596
+ byteSize: 1024
597
+ }
598
+ ]
599
+ }
600
+ };
601
+ const req = { entities: [calendarEventEntity] };
602
+ const expected = { success: true, results: [{ entityId: 'calendar-event-1', success: true }] };
603
+ mockFetch.mockResolvedValueOnce({
604
+ ok: true,
605
+ json: () => Promise.resolve(expected)
606
+ });
607
+ const result = await graphClient.setEntities(req);
608
+ expect(mockFetch).toHaveBeenCalledWith('/graph/connector/api/v1/entities/bulk', expect.objectContaining({ method: 'POST' }));
609
+ expect(result).toEqual(expected);
610
+ });
611
+ it('posts comment entities to /api/v1/entities/bulk and returns response', async () => {
612
+ const commentEntity = {
613
+ schemaVersion: '2.0',
614
+ id: 'comment-1',
615
+ updateSequenceNumber: 1,
616
+ displayName: 'Great work on this feature!',
617
+ url: 'https://example.com/comments/comment-1',
618
+ createdAt: '2024-07-09T14:27:37.000Z',
619
+ createdBy: {
620
+ id: 'ari:cloud:identity::user/user-789',
621
+ externalId: 'user-789',
622
+ displayName: 'Jane Developer'
623
+ },
624
+ lastUpdatedAt: '2024-07-09T14:27:37.000Z',
625
+ permissions: {
626
+ accessControls: [
627
+ {
628
+ principals: [
629
+ {
630
+ type: 'EVERYONE'
631
+ }
632
+ ]
633
+ }
634
+ ]
635
+ },
636
+ 'atlassian:comment': {
637
+ text: 'This is a really well-implemented feature. The code is clean and the tests are comprehensive. Thanks for the hard work!',
638
+ reactionsV2: [
639
+ {
640
+ reactionType: 'like',
641
+ total: 5
642
+ }
643
+ ]
644
+ }
645
+ };
646
+ const req = { entities: [commentEntity] };
647
+ const expected = { success: true, results: [{ entityId: 'comment-1', success: true }] };
648
+ mockFetch.mockResolvedValueOnce({
649
+ ok: true,
650
+ json: () => Promise.resolve(expected)
651
+ });
652
+ const result = await graphClient.setEntities(req);
653
+ expect(mockFetch).toHaveBeenCalledWith('/graph/connector/api/v1/entities/bulk', expect.objectContaining({ method: 'POST' }));
654
+ expect(result).toEqual(expected);
655
+ });
656
+ it('posts conversation entities to /api/v1/entities/bulk and returns response', async () => {
657
+ const conversationEntity = {
658
+ schemaVersion: '2.0',
659
+ id: 'conversation-1',
660
+ updateSequenceNumber: 1,
661
+ displayName: 'Development Team Chat',
662
+ description: 'Main channel for the development team discussions',
663
+ url: 'https://slack.com/channels/dev-team',
664
+ createdAt: '2024-07-09T14:27:37.000Z',
665
+ lastUpdatedAt: '2024-07-09T14:27:37.000Z',
666
+ permissions: {
667
+ accessControls: [
668
+ {
669
+ principals: [
670
+ {
671
+ type: 'EVERYONE'
672
+ }
673
+ ]
674
+ }
675
+ ]
676
+ },
677
+ 'atlassian:conversation': {
678
+ type: 'channel',
679
+ membershipType: 'public',
680
+ workspace: 'atlassian-workspace',
681
+ topic: 'Daily standup and development discussions',
682
+ isArchived: false,
683
+ members: [
684
+ {
685
+ accountId: 'user-123',
686
+ ari: 'ari:cloud:identity::user/user-123',
687
+ externalId: 'user-123',
688
+ name: 'John Developer',
689
+ email: 'john.developer@example.com'
690
+ },
691
+ {
692
+ accountId: 'user-456',
693
+ ari: 'ari:cloud:identity::user/user-456',
694
+ externalId: 'user-456',
695
+ name: 'Jane Developer',
696
+ email: 'jane.developer@example.com'
697
+ },
698
+ {
699
+ accountId: 'user-789',
700
+ ari: 'ari:cloud:identity::user/user-789',
701
+ externalId: 'user-789',
702
+ name: 'Bob Manager',
703
+ email: 'bob.manager@example.com'
704
+ }
705
+ ],
706
+ lastActive: '2024-07-09T16:30:00.000Z',
707
+ memberCount: 3
708
+ }
709
+ };
710
+ const req = { entities: [conversationEntity] };
711
+ const expected = { success: true, results: [{ entityId: 'conversation-1', success: true }] };
712
+ mockFetch.mockResolvedValueOnce({
713
+ ok: true,
714
+ json: () => Promise.resolve(expected)
715
+ });
716
+ const result = await graphClient.setEntities(req);
717
+ expect(mockFetch).toHaveBeenCalledWith('/graph/connector/api/v1/entities/bulk', expect.objectContaining({ method: 'POST' }));
718
+ expect(result).toEqual(expected);
719
+ });
720
+ it('posts customer org entities to /api/v1/entities/bulk and returns response', async () => {
721
+ const customerOrgEntity = {
722
+ schemaVersion: '2.0',
723
+ id: 'customer-org-1',
724
+ updateSequenceNumber: 1,
725
+ displayName: 'Acme Corporation',
726
+ description: 'Leading technology company specializing in enterprise solutions',
727
+ url: 'https://crm.example.com/customers/acme-corp',
728
+ createdAt: '2024-07-09T14:27:37.000Z',
729
+ lastUpdatedAt: '2024-07-09T14:27:37.000Z',
730
+ permissions: {
731
+ accessControls: [
732
+ {
733
+ principals: [
734
+ {
735
+ type: 'EVERYONE'
736
+ }
737
+ ]
738
+ }
739
+ ]
740
+ },
741
+ 'atlassian:customer-org': {
742
+ contacts: [
743
+ {
744
+ accountId: 'contact-123',
745
+ ari: 'ari:cloud:identity::user/contact-123',
746
+ externalId: 'contact-123',
747
+ name: 'Alice Johnson',
748
+ email: 'alice.johnson@acme.com',
749
+ avatar: 'https://avatar.example.com/alice.jpg'
750
+ },
751
+ {
752
+ accountId: 'contact-456',
753
+ ari: 'ari:cloud:identity::user/contact-456',
754
+ externalId: 'contact-456',
755
+ name: 'Bob Smith',
756
+ email: 'bob.smith@acme.com'
757
+ }
758
+ ],
759
+ customerOrgLastActivity: {
760
+ lastActivityAt: '2024-07-09T16:30:00.000Z',
761
+ event: 'Support ticket created for enterprise integration'
762
+ },
763
+ customerOrgLifeTimeValue: {
764
+ value: 250000.0,
765
+ currencyCode: 'USD'
766
+ },
767
+ websiteUrl: 'https://www.acme.com',
768
+ industry: 'Technology',
769
+ contributors: [
770
+ {
771
+ accountId: 'contributor-789',
772
+ ari: 'ari:cloud:identity::user/contributor-789',
773
+ externalId: 'contributor-789',
774
+ name: 'Charlie Brown',
775
+ email: 'charlie.brown@ourcompany.com',
776
+ userName: 'cbrown'
777
+ }
778
+ ],
779
+ country: 'United States',
780
+ accountType: 'Enterprise'
781
+ }
782
+ };
783
+ const req = { entities: [customerOrgEntity] };
784
+ const expected = { success: true, results: [{ entityId: 'customer-org-1', success: true }] };
785
+ mockFetch.mockResolvedValueOnce({
786
+ ok: true,
787
+ json: () => Promise.resolve(expected)
788
+ });
789
+ const result = await graphClient.setEntities(req);
790
+ expect(mockFetch).toHaveBeenCalledWith('/graph/connector/api/v1/entities/bulk', expect.objectContaining({ method: 'POST' }));
791
+ expect(result).toEqual(expected);
792
+ });
793
+ it('posts deal entities to /api/v1/entities/bulk and returns response', async () => {
794
+ const dealEntity = {
795
+ schemaVersion: '2.0',
796
+ id: 'deal-1',
797
+ updateSequenceNumber: 1,
798
+ displayName: 'Enterprise Software License Deal',
799
+ description: 'Large enterprise deal for software licensing and support services',
800
+ url: 'https://crm.example.com/deals/enterprise-software-123',
801
+ createdAt: '2024-07-09T14:27:37.000Z',
802
+ lastUpdatedAt: '2024-07-09T14:27:37.000Z',
803
+ permissions: {
804
+ accessControls: [
805
+ {
806
+ principals: [
807
+ {
808
+ type: 'EVERYONE'
809
+ }
810
+ ]
811
+ }
812
+ ]
813
+ },
814
+ 'atlassian:deal': {
815
+ contact: {
816
+ accountId: 'contact-deal-123',
817
+ ari: 'ari:cloud:identity::user/contact-deal-123',
818
+ externalId: 'contact-deal-123',
819
+ name: 'Sarah Wilson',
820
+ email: 'sarah.wilson@techcorp.com',
821
+ avatar: 'https://avatar.example.com/sarah.jpg'
822
+ },
823
+ stage: 'Proposal Sent',
824
+ status: 'Active',
825
+ accountName: 'TechCorp Enterprises',
826
+ contributors: [
827
+ {
828
+ accountId: 'contributor-789',
829
+ ari: 'ari:cloud:identity::user/contributor-789',
830
+ externalId: 'contributor-789',
831
+ name: 'Alex Rodriguez',
832
+ email: 'alex.rodriguez@ourcompany.com',
833
+ userName: 'arodriguez'
834
+ },
835
+ {
836
+ accountId: 'contributor-456',
837
+ ari: 'ari:cloud:identity::user/contributor-456',
838
+ externalId: 'contributor-456',
839
+ name: 'Jessica Chen',
840
+ email: 'jessica.chen@ourcompany.com',
841
+ userName: 'jchen'
842
+ }
843
+ ],
844
+ lastActivity: {
845
+ lastActivityAt: '2024-07-09T16:30:00.000Z',
846
+ event: 'Contract proposal sent to client for review'
847
+ },
848
+ dealClosedAt: '2024-08-15T10:00:00.000Z',
849
+ opportunityAmount: {
850
+ value: 500000.0,
851
+ currencyCode: 'USD'
852
+ },
853
+ isClosed: false
854
+ }
855
+ };
856
+ const req = { entities: [dealEntity] };
857
+ const expected = { success: true, results: [{ entityId: 'deal-1', success: true }] };
858
+ mockFetch.mockResolvedValueOnce({
859
+ ok: true,
860
+ json: () => Promise.resolve(expected)
861
+ });
862
+ const result = await graphClient.setEntities(req);
863
+ expect(mockFetch).toHaveBeenCalledWith('/graph/connector/api/v1/entities/bulk', expect.objectContaining({ method: 'POST' }));
864
+ expect(result).toEqual(expected);
865
+ });
866
+ it('posts deployment entities to /api/v1/entities/bulk and returns response', async () => {
867
+ const deploymentEntity = {
868
+ schemaVersion: '2.0',
869
+ id: 'deployment-1',
870
+ updateSequenceNumber: 1,
871
+ displayName: 'Production API Deployment v2.4.5',
872
+ description: 'Deployment of version 2.4.5 to production environment',
873
+ url: 'https://ci.example.com/deployments/prod-api-v2.4.5',
874
+ createdAt: '2024-07-09T14:27:37.000Z',
875
+ lastUpdatedAt: '2024-07-09T14:27:37.000Z',
876
+ permissions: {
877
+ accessControls: [
878
+ {
879
+ principals: [
880
+ {
881
+ type: 'EVERYONE'
882
+ }
883
+ ]
884
+ }
885
+ ]
886
+ },
887
+ 'atlassian:deployment': {
888
+ deploymentSequenceNumber: 12345,
889
+ label: 'v2.4.5 - Bug fixes and performance improvements',
890
+ state: 'successful',
891
+ duration: 1200,
892
+ pipeline: {
893
+ id: 'pipeline-prod-api',
894
+ displayName: 'Production API Pipeline',
895
+ url: 'https://ci.example.com/pipelines/prod-api'
896
+ },
897
+ environment: {
898
+ id: 'env-production',
899
+ displayName: 'Production Environment',
900
+ type: 'production'
901
+ },
902
+ commands: [
903
+ {
904
+ command: 'npm install --production'
905
+ },
906
+ {
907
+ command: 'npm run build'
908
+ },
909
+ {
910
+ command: 'kubectl apply -f deployment.yaml'
911
+ }
912
+ ],
913
+ triggeredBy: {
914
+ accountId: 'devops-user-123',
915
+ ari: 'ari:cloud:identity::user/devops-user-123',
916
+ externalId: 'devops-user-123',
917
+ name: 'DevOps Team',
918
+ email: 'devops@example.com',
919
+ userName: 'devops-team',
920
+ avatar: 'https://avatar.example.com/devops.jpg'
921
+ },
922
+ region: 'us-east-1'
923
+ }
924
+ };
925
+ const req = { entities: [deploymentEntity] };
926
+ const expected = { success: true, results: [{ entityId: 'deployment-1', success: true }] };
927
+ mockFetch.mockResolvedValueOnce({
928
+ ok: true,
929
+ json: () => Promise.resolve(expected)
930
+ });
931
+ const result = await graphClient.setEntities(req);
932
+ expect(mockFetch).toHaveBeenCalledWith('/graph/connector/api/v1/entities/bulk', expect.objectContaining({ method: 'POST' }));
933
+ expect(result).toEqual(expected);
934
+ });
935
+ it('posts design entities to /api/v1/entities/bulk and returns response', async () => {
936
+ const designEntity = {
937
+ schemaVersion: '2.0',
938
+ id: 'design-1',
939
+ updateSequenceNumber: 1,
940
+ displayName: 'Mobile App Login Screen',
941
+ description: 'Updated login screen design for mobile app with improved UX',
942
+ url: 'https://figma.com/design/mobile-login-screen',
943
+ createdAt: '2024-07-09T14:27:37.000Z',
944
+ lastUpdatedAt: '2024-07-09T14:27:37.000Z',
945
+ permissions: {
946
+ accessControls: [
947
+ {
948
+ principals: [
949
+ {
950
+ type: 'EVERYONE'
951
+ }
952
+ ]
953
+ }
954
+ ]
955
+ },
956
+ 'atlassian:design': {
957
+ liveEmbedUrl: 'https://figma.com/embed/mobile-login-screen',
958
+ inspectUrl: 'https://figma.com/inspect/mobile-login-screen',
959
+ status: 'Ready for Development',
960
+ type: 'UI Design',
961
+ iconUrl: 'https://figma.com/icons/mobile-login-screen.svg'
962
+ }
963
+ };
964
+ const req = { entities: [designEntity] };
965
+ const expected = { success: true, results: [{ entityId: 'design-1', success: true }] };
966
+ mockFetch.mockResolvedValueOnce({
967
+ ok: true,
968
+ json: () => Promise.resolve(expected)
969
+ });
970
+ const result = await graphClient.setEntities(req);
971
+ expect(mockFetch).toHaveBeenCalledWith('/graph/connector/api/v1/entities/bulk', expect.objectContaining({ method: 'POST' }));
972
+ expect(result).toEqual(expected);
973
+ });
974
+ it('posts position entities to /api/v1/entities/bulk and returns response', async () => {
975
+ const positionEntity = {
976
+ schemaVersion: '2.0',
977
+ id: 'position-1',
978
+ updateSequenceNumber: 1,
979
+ displayName: 'Senior Software Engineer - Backend',
980
+ url: 'https://hr.example.com/positions/senior-backend-engineer',
981
+ createdAt: '2024-07-09T14:27:37.000Z',
982
+ lastUpdatedAt: '2024-07-09T14:27:37.000Z',
983
+ permissions: {
984
+ accessControls: [
985
+ {
986
+ principals: [
987
+ {
988
+ type: 'EVERYONE'
989
+ }
990
+ ]
991
+ }
992
+ ]
993
+ },
994
+ parentKey: {
995
+ type: 'atlassian:organisation',
996
+ value: {
997
+ entityId: 'org-tech-company-123'
998
+ }
999
+ },
1000
+ associations: {
1001
+ set: [
1002
+ {
1003
+ associationType: 'atlassian:worker',
1004
+ values: ['worker-alice-johnson-456']
1005
+ }
1006
+ ]
1007
+ },
1008
+ 'atlassian:position': {
1009
+ customAndSensitiveData: 'Salary range: $120,000-$150,000. Remote work eligible.',
1010
+ status: 'Open',
1011
+ jobTitle: 'Senior Software Engineer - Backend'
1012
+ }
1013
+ };
1014
+ const req = { entities: [positionEntity] };
1015
+ const expected = { success: true, results: [{ entityId: 'position-1', success: true }] };
1016
+ mockFetch.mockResolvedValueOnce({
1017
+ ok: true,
1018
+ json: () => Promise.resolve(expected)
1019
+ });
1020
+ const result = await graphClient.setEntities(req);
1021
+ expect(mockFetch).toHaveBeenCalledWith('/graph/connector/api/v1/entities/bulk', expect.objectContaining({ method: 'POST' }));
1022
+ expect(result).toEqual(expected);
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
+ });
482
1627
  });