@elisra-devops/docgen-data-provider 1.69.14 → 1.69.16

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.
@@ -681,7 +681,7 @@ describe('PipelinesDataProvider', () => {
681
681
  });
682
682
 
683
683
  describe('getPipelineResourcePipelinesFromObject', () => {
684
- it('should return empty set when no pipeline resources exist', async () => {
684
+ it('should return empty array when no pipeline resources exist', async () => {
685
685
  // Arrange
686
686
  const inPipeline = {
687
687
  resources: {},
@@ -691,7 +691,7 @@ describe('PipelinesDataProvider', () => {
691
691
  const result = await pipelinesDataProvider.getPipelineResourcePipelinesFromObject(inPipeline);
692
692
 
693
693
  // Assert
694
- expect(result).toEqual(new Set());
694
+ expect(result).toEqual([]);
695
695
  });
696
696
 
697
697
  it('should extract pipeline resources from pipeline object', async () => {
@@ -714,11 +714,11 @@ describe('PipelinesDataProvider', () => {
714
714
  id: 789,
715
715
  definition: { id: 123, type: 'build' },
716
716
  buildNumber: '20231201.1',
717
- project: { name: 'project1' },
717
+ project: { name: 'project' },
718
718
  repository: { type: 'TfsGit' },
719
719
  };
720
720
 
721
- (TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockBuildResponse);
721
+ (TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockBuildResponse); // fixed-url attempt
722
722
 
723
723
  // Act
724
724
  const result = await pipelinesDataProvider.getPipelineResourcePipelinesFromObject(inPipeline);
@@ -730,7 +730,7 @@ describe('PipelinesDataProvider', () => {
730
730
  buildId: 789,
731
731
  definitionId: 123,
732
732
  buildNumber: '20231201.1',
733
- teamProject: 'project1',
733
+ teamProject: 'project',
734
734
  provider: 'TfsGit',
735
735
  });
736
736
  });
@@ -760,226 +760,35 @@ describe('PipelinesDataProvider', () => {
760
760
  expect(result).toEqual([]);
761
761
  });
762
762
 
763
- it('should resolve resource pipeline by buildNumber when runId is missing and version is semantic', async () => {
764
- // Arrange
765
- const inPipeline = {
766
- url: 'https://dev.azure.com/org/project1/_apis/pipelines/10/runs/200',
767
- resources: {
768
- pipelines: {
769
- SOME_PACKAGE: {
770
- pipeline: {
771
- id: 123,
772
- url: 'https://dev.azure.com/org/project1/_apis/pipelines/123?revision=1',
773
- },
774
- project: { name: 'project1' },
775
- source: 'project1-system-package',
776
- version: '1.0.56',
777
- branch: 'main',
778
- },
779
- },
780
- },
781
- } as unknown as PipelineRun;
782
-
783
- const mockListBuildsResponse = {
784
- value: [
785
- {
786
- id: 789,
787
- },
788
- ],
789
- };
790
- const mockBuildResponse = {
791
- id: 789,
792
- definition: { id: 123, type: 'build' },
793
- buildNumber: '1.0.56',
794
- project: { name: 'project1' },
795
- repository: { type: 'TfsGit' },
796
- };
797
-
798
- (TFSServices.getItemContent as jest.Mock)
799
- .mockResolvedValueOnce(mockListBuildsResponse) // findBuildByDefinitionAndBuildNumber
800
- .mockResolvedValueOnce(mockBuildResponse); // getPipelineBuildByBuildId
801
-
802
- // Act
803
- const result = await pipelinesDataProvider.getPipelineResourcePipelinesFromObject(inPipeline);
804
-
805
- // Assert
806
- expect(result).toHaveLength(1);
807
- expect((result as any[])[0]).toEqual({
808
- name: 'SOME_PACKAGE',
809
- buildId: 789,
810
- definitionId: 123,
811
- buildNumber: '1.0.56',
812
- teamProject: 'project1',
813
- provider: 'TfsGit',
814
- });
815
-
816
- // Ensure branch normalization was applied in the build search URL
817
- expect((TFSServices.getItemContent as jest.Mock).mock.calls[0][0]).toContain('branchName=refs%2Fheads%2Fmain');
818
- expect((TFSServices.getItemContent as jest.Mock).mock.calls[0][0]).toContain('buildNumber=1.0.56');
819
- expect((TFSServices.getItemContent as jest.Mock).mock.calls[0][0]).toContain('definitions=123');
820
- });
821
-
822
- it('should fall back to pipeline run history when buildNumber lookup returns no builds', async () => {
823
- // Arrange
824
- const inPipeline = {
825
- url: 'https://dev.azure.com/org/project1/_apis/pipelines/10/runs/200',
826
- resources: {
827
- pipelines: {
828
- SOME_PACKAGE: {
829
- pipeline: {
830
- id: 123,
831
- url: 'https://dev.azure.com/org/project1/_apis/pipelines/123?revision=1',
832
- },
833
- project: { name: 'project1' },
834
- source: 'project1-system-package',
835
- version: '20251109.1',
836
- branch: 'main',
837
- },
838
- },
839
- },
840
- } as unknown as PipelineRun;
841
-
842
- const mockListBuildsResponseEmpty = { value: [] };
843
- const mockRunHistoryResponse = {
844
- value: [{ id: 789, name: '20251109.1', result: 'succeeded' }],
845
- };
846
- const mockBuildResponse = {
847
- id: 789,
848
- definition: { id: 123, type: 'build' },
849
- buildNumber: '20251109.1',
850
- project: { name: 'project1' },
851
- repository: { type: 'TfsGit' },
852
- };
853
-
854
- (TFSServices.getItemContent as jest.Mock)
855
- .mockResolvedValueOnce(mockListBuildsResponseEmpty) // findBuildByDefinitionAndBuildNumber
856
- .mockResolvedValueOnce(mockListBuildsResponseEmpty) // findBuildByBuildNumber (no definition)
857
- .mockResolvedValueOnce(mockRunHistoryResponse) // GetPipelineRunHistory
858
- .mockResolvedValueOnce(mockBuildResponse); // getPipelineBuildByBuildId
859
-
860
- // Act
861
- const result = await pipelinesDataProvider.getPipelineResourcePipelinesFromObject(inPipeline);
862
-
863
- // Assert
864
- expect(result).toHaveLength(1);
865
- expect((result as any[])[0]).toEqual({
866
- name: 'SOME_PACKAGE',
867
- buildId: 789,
868
- definitionId: 123,
869
- buildNumber: '20251109.1',
870
- teamProject: 'project1',
871
- provider: 'TfsGit',
872
- });
873
- });
874
-
875
- it('should fall back to buildNumber-only lookup when definition-based lookup returns no builds', async () => {
876
- const inPipeline = {
877
- url: 'https://dev.azure.com/org/project1/_apis/pipelines/10/runs/200',
878
- resources: {
879
- pipelines: {
880
- SOME_PACKAGE: {
881
- pipeline: {
882
- id: 770,
883
- url: 'https://dev.azure.com/org/project1/_apis/pipelines/770?revision=1',
884
- },
885
- project: { name: 'project1' },
886
- source: 'project1-system-package',
887
- version: '20251109.1',
888
- branch: 'main',
889
- },
890
- },
891
- },
892
- } as unknown as PipelineRun;
893
-
894
- const mockListBuildsResponseEmpty = { value: [] };
895
- const mockListBuildsByNumber = {
896
- value: [
897
- {
898
- id: 789,
899
- definition: { id: 123, name: 'project1-system-package' },
900
- },
901
- ],
902
- };
903
- const mockBuildResponse = {
904
- id: 789,
905
- definition: { id: 123, type: 'build' },
906
- buildNumber: '20251109.1',
907
- project: { name: 'project1' },
908
- repository: { type: 'TfsGit' },
909
- };
910
-
911
- (TFSServices.getItemContent as jest.Mock)
912
- .mockResolvedValueOnce(mockListBuildsResponseEmpty) // findBuildByDefinitionAndBuildNumber
913
- .mockResolvedValueOnce(mockListBuildsByNumber) // findBuildByBuildNumber (no definition)
914
- .mockResolvedValueOnce(mockBuildResponse); // getPipelineBuildByBuildId
915
-
916
- const result = await pipelinesDataProvider.getPipelineResourcePipelinesFromObject(inPipeline);
917
-
918
- expect(result).toHaveLength(1);
919
- expect((result as any[])[0]).toEqual({
920
- name: 'SOME_PACKAGE',
921
- buildId: 789,
922
- definitionId: 123,
923
- buildNumber: '20251109.1',
924
- teamProject: 'project1',
925
- provider: 'TfsGit',
926
- });
927
-
928
- // Ensure the second call is the buildNumber-only lookup (no definitions= filter)
929
- expect((TFSServices.getItemContent as jest.Mock).mock.calls[1][0]).toContain('buildNumber=20251109.1');
930
- expect((TFSServices.getItemContent as jest.Mock).mock.calls[1][0]).not.toContain('definitions=');
931
- });
932
-
933
- it('should resolve project name when pipeline resource provides project id (GUID)', async () => {
763
+ it('should skip resource when fixed-url returns a build from a different project/pipeline', async () => {
934
764
  const inPipeline = {
935
- url: 'https://dev.azure.com/org/project1/_apis/pipelines/10/runs/200',
936
765
  resources: {
937
766
  pipelines: {
938
- SOME_PACKAGE: {
767
+ myPipeline: {
939
768
  pipeline: {
940
769
  id: 123,
941
- url: 'https://dev.azure.com/org/1488cb19-7369-4afc-92bf-251d368b85be/_apis/pipelines/123?revision=1',
770
+ name: 'ExpectedPipeline',
771
+ url: 'https://dev.azure.com/org/project/_apis/pipelines/123?revision=1',
942
772
  },
943
- project: { name: '1488cb19-7369-4afc-92bf-251d368b85be' },
944
- source: 'project1-system-package',
945
- version: '20251109.1',
946
- branch: 'main',
947
773
  },
948
774
  },
949
775
  },
950
776
  } as unknown as PipelineRun;
951
777
 
952
- const mockProjectResponse = { id: '1488cb19-7369-4afc-92bf-251d368b85be', name: 'Test CMMI' };
953
- const mockListBuildsResponse = { value: [{ id: 789 }] };
954
778
  const mockBuildResponse = {
955
779
  id: 789,
956
- definition: { id: 123, type: 'build' },
957
- buildNumber: '20251109.1',
958
- project: { name: 'Test CMMI' },
780
+ definition: { id: 999, name: 'DifferentPipeline', type: 'build' },
781
+ buildNumber: '20231201.1',
782
+ project: { name: 'DifferentProject' },
959
783
  repository: { type: 'TfsGit' },
960
784
  };
961
785
 
962
- (TFSServices.getItemContent as jest.Mock)
963
- .mockResolvedValueOnce(mockProjectResponse) // normalizeProjectName
964
- .mockResolvedValueOnce(mockListBuildsResponse) // findBuildByDefinitionAndBuildNumber
965
- .mockResolvedValueOnce(mockBuildResponse); // getPipelineBuildByBuildId
786
+ (TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockBuildResponse);
966
787
 
967
788
  const result = await pipelinesDataProvider.getPipelineResourcePipelinesFromObject(inPipeline);
968
789
 
969
- expect(result).toHaveLength(1);
970
- expect((result as any[])[0]).toEqual({
971
- name: 'SOME_PACKAGE',
972
- buildId: 789,
973
- definitionId: 123,
974
- buildNumber: '20251109.1',
975
- teamProject: 'Test CMMI',
976
- provider: 'TfsGit',
977
- });
978
-
979
- // Ensure project-id normalization API was called
980
- expect((TFSServices.getItemContent as jest.Mock).mock.calls[0][0]).toContain(
981
- `${mockOrgUrl}_apis/projects/1488cb19-7369-4afc-92bf-251d368b85be`
982
- );
790
+ expect(result).toEqual([]);
791
+ expect(TFSServices.getItemContent).toHaveBeenCalledTimes(1);
983
792
  });
984
793
  });
985
794
 
@@ -1227,6 +1036,126 @@ describe('PipelinesDataProvider', () => {
1227
1036
  });
1228
1037
  });
1229
1038
 
1039
+ describe('private helper methods', () => {
1040
+ it('tryGetTeamProjectFromAzureDevOpsUrl should extract project segment before _apis', () => {
1041
+ const fn = (pipelinesDataProvider as any).tryGetTeamProjectFromAzureDevOpsUrl.bind(
1042
+ pipelinesDataProvider
1043
+ );
1044
+ expect(fn('https://dev.azure.com/org/Test/_apis/pipelines/123?revision=1')).toBe('Test');
1045
+ expect(fn('https://dev.azure.com/_apis/pipelines/123?revision=1')).toBeUndefined();
1046
+ expect(fn('https://dev.azure.com/org/_apis/pipelines/123?revision=1')).toBe('org');
1047
+ expect(fn('not-a-url')).toBeUndefined();
1048
+ });
1049
+
1050
+ it('tryBuildBuildApiUrlFromPipelinesApiUrl should rewrite pipelines url to builds url and drop query', () => {
1051
+ const fn = (pipelinesDataProvider as any).tryBuildBuildApiUrlFromPipelinesApiUrl.bind(
1052
+ pipelinesDataProvider
1053
+ );
1054
+ expect(fn('https://dev.azure.com/org/Test/_apis/pipelines/123?revision=1')).toBe(
1055
+ 'https://dev.azure.com/org/Test/_apis/build/builds/123'
1056
+ );
1057
+ expect(fn('https://dev.azure.com/org/Test/_apis/build/builds/123')).toBeUndefined();
1058
+ expect(fn('not-a-url')).toBeUndefined();
1059
+ });
1060
+
1061
+ it('tryParseRunIdFromUrl should parse run id from pipelines runs URL and build id from builds URL', () => {
1062
+ const fn = (pipelinesDataProvider as any).tryParseRunIdFromUrl.bind(pipelinesDataProvider);
1063
+ expect(fn('https://dev.azure.com/org/Test/_apis/pipelines/10/runs/555')).toBe(555);
1064
+ expect(fn('https://dev.azure.com/org/Test/_apis/build/builds/777')).toBe(777);
1065
+ expect(fn('https://dev.azure.com/org/Test/_apis/pipelines/10/runs/not-a-number')).toBeUndefined();
1066
+ expect(fn('not-a-url')).toBeUndefined();
1067
+ });
1068
+
1069
+ it('normalizeBranchName should normalize branch to refs/heads form', () => {
1070
+ const fn = (pipelinesDataProvider as any).normalizeBranchName.bind(pipelinesDataProvider);
1071
+ expect(fn('main')).toBe('refs/heads/main');
1072
+ expect(fn('heads/main')).toBe('refs/heads/main');
1073
+ expect(fn('refs/heads/main')).toBe('refs/heads/main');
1074
+ expect(fn('')).toBeUndefined();
1075
+ expect(fn(undefined)).toBeUndefined();
1076
+ });
1077
+
1078
+ it('normalizeProjectName should resolve GUID project id to name and cache it', async () => {
1079
+ const guid = '009c6fae-b000-47fe-994e-be3354b78fbc';
1080
+ (TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce({ name: 'ResolvedProjectName' });
1081
+
1082
+ const fn = (pipelinesDataProvider as any).normalizeProjectName.bind(pipelinesDataProvider);
1083
+ const first = await fn(guid);
1084
+ const second = await fn(guid);
1085
+
1086
+ expect(first).toBe('ResolvedProjectName');
1087
+ expect(second).toBe('ResolvedProjectName');
1088
+ expect(TFSServices.getItemContent).toHaveBeenCalledTimes(1);
1089
+ expect(TFSServices.getItemContent).toHaveBeenCalledWith(
1090
+ `${mockOrgUrl}_apis/projects/${encodeURIComponent(guid)}?api-version=6.0`,
1091
+ mockToken,
1092
+ 'get',
1093
+ null,
1094
+ null,
1095
+ false
1096
+ );
1097
+ });
1098
+
1099
+ it('normalizeProjectName should return raw GUID when project resolution fails', async () => {
1100
+ const guid = '009c6fae-b000-47fe-994e-be3354b78fbc';
1101
+ (TFSServices.getItemContent as jest.Mock).mockRejectedValueOnce(new Error('boom'));
1102
+ const fn = (pipelinesDataProvider as any).normalizeProjectName.bind(pipelinesDataProvider);
1103
+ const result = await fn(guid);
1104
+ expect(result).toBe(guid);
1105
+ });
1106
+
1107
+ it('findBuildByBuildNumber should prefer matching definition name when provided', async () => {
1108
+ (TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce({
1109
+ value: [
1110
+ { id: 1, definition: { name: 'Other' } },
1111
+ { id: 2, definition: { name: 'Expected' } },
1112
+ ],
1113
+ });
1114
+
1115
+ const fn = (pipelinesDataProvider as any).findBuildByBuildNumber.bind(pipelinesDataProvider);
1116
+ const result = await fn('project1', '20251225.2', 'main', 'Expected');
1117
+ expect(result?.id).toBe(2);
1118
+ });
1119
+
1120
+ it('tryGetBuildByIdWithFallback should fall back to non-project-scoped URL when project-scoped lookup fails', async () => {
1121
+ jest
1122
+ .spyOn(pipelinesDataProvider as any, 'getPipelineBuildByBuildId')
1123
+ .mockRejectedValueOnce(new Error('not found'));
1124
+ (TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce({ id: 123 });
1125
+
1126
+ const fn = (pipelinesDataProvider as any).tryGetBuildByIdWithFallback.bind(pipelinesDataProvider);
1127
+ const result = await fn('project1', 123);
1128
+
1129
+ expect(result).toEqual({ id: 123 });
1130
+ expect(TFSServices.getItemContent).toHaveBeenCalledWith(
1131
+ `${mockOrgUrl}_apis/build/builds/123`,
1132
+ mockToken,
1133
+ 'get',
1134
+ null,
1135
+ null
1136
+ );
1137
+ });
1138
+
1139
+ it('getPipelineResourcePipelinesFromObject should skip resource when pipelines url cannot be converted to builds url', async () => {
1140
+ const inPipeline = {
1141
+ resources: {
1142
+ pipelines: {
1143
+ myPipeline: {
1144
+ pipeline: {
1145
+ id: 123,
1146
+ url: 'https://dev.azure.com/org/project/_apis/build/builds/123',
1147
+ },
1148
+ },
1149
+ },
1150
+ },
1151
+ } as unknown as PipelineRun;
1152
+
1153
+ const result = await pipelinesDataProvider.getPipelineResourcePipelinesFromObject(inPipeline);
1154
+ expect(result).toEqual([]);
1155
+ expect(TFSServices.getItemContent).not.toHaveBeenCalled();
1156
+ });
1157
+ });
1158
+
1230
1159
  describe('isInvalidPipelineRun', () => {
1231
1160
  const invokeIsInvalidPipelineRun = (
1232
1161
  pipelineRun: any,
@@ -20,8 +20,8 @@ describe('TicketsDataProvider', () => {
20
20
  });
21
21
 
22
22
  describe('isLinkSideAllowedByTypeOrId', () => {
23
- it('should accept when allowedTypes is empty and field is present', async () => {
24
- const wiql = "SELECT * FROM WorkItemLinks WHERE Source.[System.WorkItemType] = 'Epic'";
23
+ it('should return true when source types include allowed types', async () => {
24
+ const wiql = "SELECT * FROM WorkItemLinks WHERE Source.[System.WorkItemType] IN ('Bug')";
25
25
  const result = await (ticketsDataProvider as any).isLinkSideAllowedByTypeOrId(
26
26
  {},
27
27
  wiql,
@@ -81,6 +81,18 @@ describe('TicketsDataProvider', () => {
81
81
  });
82
82
  });
83
83
 
84
+ describe('fetchSystemRequirementQueries', () => {
85
+ it('should include Task in allowed types', async () => {
86
+ const structureSpy = jest
87
+ .spyOn(ticketsDataProvider as any, 'structureFetchedQueries')
88
+ .mockResolvedValue({ tree1: { id: 't1' }, tree2: null });
89
+
90
+ await (ticketsDataProvider as any).fetchSystemRequirementQueries({ hasChildren: false }, []);
91
+
92
+ expect(structureSpy.mock.calls[0][3]).toEqual(['epic', 'feature', 'requirement', 'task']);
93
+ });
94
+ });
95
+
84
96
  describe('findChildFolderByPossibleNames', () => {
85
97
  it('should return null when parent is missing or possibleNames is empty', async () => {
86
98
  await expect(
@@ -641,7 +653,12 @@ describe('TicketsDataProvider', () => {
641
653
 
642
654
  it('should reject when WIQL contains a type outside allowedTypes', async () => {
643
655
  const wiql = "SELECT * FROM WorkItems WHERE [System.WorkItemType] IN ('Bug','Task')";
644
- const res = await (ticketsDataProvider as any).isFlatQueryAllowedByTypeOrId({}, wiql, ['Bug'], new Map());
656
+ const res = await (ticketsDataProvider as any).isFlatQueryAllowedByTypeOrId(
657
+ {},
658
+ wiql,
659
+ ['Bug'],
660
+ new Map()
661
+ );
645
662
  expect(res).toBe(false);
646
663
  });
647
664
  });