@elisra-devops/docgen-data-provider 1.74.0 → 1.76.0
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/bin/models/mewp-reporting.d.ts +120 -0
- package/bin/models/mewp-reporting.js +3 -0
- package/bin/models/mewp-reporting.js.map +1 -0
- package/bin/modules/ResultDataProvider.d.ts +47 -13
- package/bin/modules/ResultDataProvider.js +1122 -257
- package/bin/modules/ResultDataProvider.js.map +1 -1
- package/bin/tests/modules/ResultDataProvider.test.js +1293 -113
- package/bin/tests/modules/ResultDataProvider.test.js.map +1 -1
- package/bin/utils/mewpExternalIngestionUtils.d.ts +17 -0
- package/bin/utils/mewpExternalIngestionUtils.js +197 -0
- package/bin/utils/mewpExternalIngestionUtils.js.map +1 -0
- package/bin/utils/mewpExternalTableUtils.d.ts +36 -0
- package/bin/utils/mewpExternalTableUtils.js +320 -0
- package/bin/utils/mewpExternalTableUtils.js.map +1 -0
- package/package.json +10 -1
- package/src/models/mewp-reporting.ts +138 -0
- package/src/modules/ResultDataProvider.ts +1325 -353
- package/src/tests/modules/ResultDataProvider.test.ts +1481 -138
- package/src/utils/mewpExternalIngestionUtils.ts +270 -0
- package/src/utils/mewpExternalTableUtils.ts +461 -0
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const tfs_1 = require("../../helpers/tfs");
|
|
4
4
|
const ResultDataProvider_1 = require("../../modules/ResultDataProvider");
|
|
5
5
|
const logger_1 = require("../../utils/logger");
|
|
6
|
+
const axios_1 = require("axios");
|
|
7
|
+
const XLSX = require("xlsx");
|
|
6
8
|
// Mock dependencies
|
|
7
9
|
jest.mock('../../helpers/tfs');
|
|
8
10
|
jest.mock('../../utils/logger');
|
|
@@ -22,6 +24,12 @@ describe('ResultDataProvider', () => {
|
|
|
22
24
|
const mockToken = 'mock-token';
|
|
23
25
|
const mockProjectName = 'test-project';
|
|
24
26
|
const mockTestPlanId = '12345';
|
|
27
|
+
const buildWorkbookBuffer = (rows) => {
|
|
28
|
+
const worksheet = XLSX.utils.aoa_to_sheet(rows);
|
|
29
|
+
const workbook = XLSX.utils.book_new();
|
|
30
|
+
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
|
|
31
|
+
return XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' });
|
|
32
|
+
};
|
|
25
33
|
beforeEach(() => {
|
|
26
34
|
jest.clearAllMocks();
|
|
27
35
|
resultDataProvider = new ResultDataProvider_1.default(mockOrgUrl, mockToken);
|
|
@@ -857,85 +865,6 @@ describe('ResultDataProvider', () => {
|
|
|
857
865
|
});
|
|
858
866
|
});
|
|
859
867
|
describe('getMewpL2CoverageFlatResults', () => {
|
|
860
|
-
it('should support query-mode requirement scope for MEWP coverage', async () => {
|
|
861
|
-
jest.spyOn(resultDataProvider, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
862
|
-
jest.spyOn(resultDataProvider, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
|
|
863
|
-
jest.spyOn(resultDataProvider, 'fetchTestData').mockResolvedValueOnce([
|
|
864
|
-
{
|
|
865
|
-
testPointsItems: [{ testCaseId: 101, lastRunId: 10, lastResultId: 20, testCaseName: 'TC 101' }],
|
|
866
|
-
testCasesItems: [
|
|
867
|
-
{
|
|
868
|
-
workItem: {
|
|
869
|
-
id: 101,
|
|
870
|
-
workItemFields: [{ key: 'System.Title', value: 'TC 101' }],
|
|
871
|
-
},
|
|
872
|
-
},
|
|
873
|
-
],
|
|
874
|
-
},
|
|
875
|
-
]);
|
|
876
|
-
jest.spyOn(resultDataProvider, 'fetchMewpRequirementTypeNames').mockResolvedValueOnce([
|
|
877
|
-
'Requirement',
|
|
878
|
-
]);
|
|
879
|
-
jest.spyOn(resultDataProvider, 'fetchWorkItemsByIds').mockResolvedValueOnce([
|
|
880
|
-
{
|
|
881
|
-
id: 9001,
|
|
882
|
-
fields: {
|
|
883
|
-
'System.WorkItemType': 'Requirement',
|
|
884
|
-
'System.Title': 'Requirement from query',
|
|
885
|
-
'Custom.CustomerId': 'SR3001',
|
|
886
|
-
'System.AreaPath': 'MEWP\\IL',
|
|
887
|
-
},
|
|
888
|
-
relations: [
|
|
889
|
-
{
|
|
890
|
-
rel: 'Microsoft.VSTS.Common.TestedBy-Forward',
|
|
891
|
-
url: 'https://dev.azure.com/org/_apis/wit/workItems/101',
|
|
892
|
-
},
|
|
893
|
-
],
|
|
894
|
-
},
|
|
895
|
-
]);
|
|
896
|
-
jest.spyOn(resultDataProvider, 'fetchAllResultDataTestReporter').mockResolvedValueOnce([
|
|
897
|
-
{
|
|
898
|
-
testCaseId: 101,
|
|
899
|
-
testCase: { id: 101, name: 'TC 101' },
|
|
900
|
-
iteration: {
|
|
901
|
-
actionResults: [{ action: 'Validate SR3001', expected: '', outcome: 'Passed' }],
|
|
902
|
-
},
|
|
903
|
-
},
|
|
904
|
-
]);
|
|
905
|
-
const TicketsProviderMock = require('../../modules/TicketsDataProvider').default;
|
|
906
|
-
TicketsProviderMock.mockImplementationOnce(() => ({
|
|
907
|
-
GetQueryResultsFromWiql: jest.fn().mockResolvedValue({
|
|
908
|
-
fetchedWorkItems: [
|
|
909
|
-
{
|
|
910
|
-
id: 9001,
|
|
911
|
-
fields: {
|
|
912
|
-
'System.WorkItemType': 'Requirement',
|
|
913
|
-
'System.Title': 'Requirement from query',
|
|
914
|
-
'Custom.CustomerId': 'SR3001',
|
|
915
|
-
'System.AreaPath': 'MEWP\\IL',
|
|
916
|
-
},
|
|
917
|
-
},
|
|
918
|
-
],
|
|
919
|
-
}),
|
|
920
|
-
}));
|
|
921
|
-
const result = await resultDataProvider.getMewpL2CoverageFlatResults('123', mockProjectName, [1], {
|
|
922
|
-
linkedQueryMode: 'query',
|
|
923
|
-
testAssociatedQuery: { wiql: { href: 'https://example.com/wiql' } },
|
|
924
|
-
});
|
|
925
|
-
const row = result.rows.find((item) => item['Customer ID'] === 'SR3001');
|
|
926
|
-
expect(row).toEqual(expect.objectContaining({
|
|
927
|
-
'Title (Customer name)': 'Requirement from query',
|
|
928
|
-
'Responsibility - SAPWBS (ESUK/IL)': 'IL',
|
|
929
|
-
'Test case id': 101,
|
|
930
|
-
'Test case title': 'TC 101',
|
|
931
|
-
'Number of passed steps': 1,
|
|
932
|
-
'Number of failed steps': 0,
|
|
933
|
-
'Number of not run tests': 0,
|
|
934
|
-
}));
|
|
935
|
-
expect(TicketsProviderMock).toHaveBeenCalled();
|
|
936
|
-
const instance = TicketsProviderMock.mock.results[0].value;
|
|
937
|
-
expect(instance.GetQueryResultsFromWiql).toHaveBeenCalledWith('https://example.com/wiql', true, expect.any(Map));
|
|
938
|
-
});
|
|
939
868
|
it('should map SR ids from steps and output requirement-test-case coverage rows', async () => {
|
|
940
869
|
jest.spyOn(resultDataProvider, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
941
870
|
jest.spyOn(resultDataProvider, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
|
|
@@ -959,6 +888,8 @@ describe('ResultDataProvider', () => {
|
|
|
959
888
|
{
|
|
960
889
|
workItemId: 5001,
|
|
961
890
|
requirementId: 'SR1001',
|
|
891
|
+
baseKey: 'SR1001',
|
|
892
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
962
893
|
title: 'Covered requirement',
|
|
963
894
|
responsibility: 'ESUK',
|
|
964
895
|
linkedTestCaseIds: [101],
|
|
@@ -966,6 +897,8 @@ describe('ResultDataProvider', () => {
|
|
|
966
897
|
{
|
|
967
898
|
workItemId: 5002,
|
|
968
899
|
requirementId: 'SR1002',
|
|
900
|
+
baseKey: 'SR1002',
|
|
901
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
969
902
|
title: 'Referenced from non-linked step text',
|
|
970
903
|
responsibility: 'IL',
|
|
971
904
|
linkedTestCaseIds: [],
|
|
@@ -973,6 +906,8 @@ describe('ResultDataProvider', () => {
|
|
|
973
906
|
{
|
|
974
907
|
workItemId: 5003,
|
|
975
908
|
requirementId: 'SR1003',
|
|
909
|
+
baseKey: 'SR1003',
|
|
910
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
976
911
|
title: 'Not covered by any test case',
|
|
977
912
|
responsibility: 'IL',
|
|
978
913
|
linkedTestCaseIds: [],
|
|
@@ -986,11 +921,11 @@ describe('ResultDataProvider', () => {
|
|
|
986
921
|
actionResults: [
|
|
987
922
|
{
|
|
988
923
|
action: 'Validate <b>S</b><b>R</b> 1 0 0 1 happy path',
|
|
989
|
-
expected: '',
|
|
924
|
+
expected: '<b>S</b><b>R</b> 1 0 0 1',
|
|
990
925
|
outcome: 'Passed',
|
|
991
926
|
},
|
|
992
|
-
{ action: 'Validate SR1001 failed flow', expected: '
|
|
993
|
-
{ action: '', expected: '
|
|
927
|
+
{ action: 'Validate SR1001 failed flow', expected: 'SR1001', outcome: 'Failed' },
|
|
928
|
+
{ action: '', expected: 'S R 1 0 0 1', outcome: 'Unspecified' },
|
|
994
929
|
],
|
|
995
930
|
},
|
|
996
931
|
},
|
|
@@ -1007,48 +942,59 @@ describe('ResultDataProvider', () => {
|
|
|
1007
942
|
stepId: '1',
|
|
1008
943
|
stepPosition: '1',
|
|
1009
944
|
action: 'Definition contains SR1002',
|
|
1010
|
-
expected: '',
|
|
945
|
+
expected: 'SR1002',
|
|
1011
946
|
isSharedStepTitle: false,
|
|
1012
947
|
},
|
|
1013
948
|
]);
|
|
1014
949
|
const result = await resultDataProvider.getMewpL2CoverageFlatResults('123', mockProjectName, [1]);
|
|
1015
950
|
expect(result).toEqual(expect.objectContaining({
|
|
1016
951
|
sheetName: expect.stringContaining('MEWP L2 Coverage'),
|
|
1017
|
-
columnOrder: expect.arrayContaining(['
|
|
952
|
+
columnOrder: expect.arrayContaining(['L2 REQ ID', 'L2 REQ Title', 'L2 Run Status']),
|
|
1018
953
|
}));
|
|
1019
|
-
const covered = result.rows.find((row) => row['
|
|
1020
|
-
const inferredByStepText = result.rows.find((row) => row['
|
|
1021
|
-
const uncovered = result.rows.find((row) => row['
|
|
1022
|
-
(row['Test case id'] === '' || row['Test case id'] === undefined || row['Test case id'] === null));
|
|
954
|
+
const covered = result.rows.find((row) => row['L2 REQ ID'] === 'SR1001');
|
|
955
|
+
const inferredByStepText = result.rows.find((row) => row['L2 REQ ID'] === 'SR1002');
|
|
956
|
+
const uncovered = result.rows.find((row) => row['L2 REQ ID'] === 'SR1003');
|
|
1023
957
|
expect(covered).toEqual(expect.objectContaining({
|
|
1024
|
-
'
|
|
1025
|
-
'
|
|
1026
|
-
'
|
|
1027
|
-
'
|
|
1028
|
-
'
|
|
1029
|
-
'
|
|
958
|
+
'L2 REQ Title': 'Covered requirement',
|
|
959
|
+
'L2 SubSystem': '',
|
|
960
|
+
'L2 Run Status': 'Fail',
|
|
961
|
+
'Bug ID': '',
|
|
962
|
+
'L3 REQ ID': '',
|
|
963
|
+
'L4 REQ ID': '',
|
|
1030
964
|
}));
|
|
1031
965
|
expect(inferredByStepText).toEqual(expect.objectContaining({
|
|
1032
|
-
'
|
|
1033
|
-
'
|
|
1034
|
-
'
|
|
1035
|
-
'Number of passed steps': 0,
|
|
1036
|
-
'Number of failed steps': 0,
|
|
1037
|
-
'Number of not run tests': 1,
|
|
966
|
+
'L2 REQ Title': 'Referenced from non-linked step text',
|
|
967
|
+
'L2 SubSystem': '',
|
|
968
|
+
'L2 Run Status': 'Not Run',
|
|
1038
969
|
}));
|
|
1039
970
|
expect(uncovered).toEqual(expect.objectContaining({
|
|
1040
|
-
'
|
|
1041
|
-
'
|
|
1042
|
-
'
|
|
1043
|
-
'Number of passed steps': 0,
|
|
1044
|
-
'Number of failed steps': 0,
|
|
1045
|
-
'Number of not run tests': 0,
|
|
971
|
+
'L2 REQ Title': 'Not covered by any test case',
|
|
972
|
+
'L2 SubSystem': '',
|
|
973
|
+
'L2 Run Status': 'Not Run',
|
|
1046
974
|
}));
|
|
1047
975
|
});
|
|
1048
976
|
it('should extract SR ids from HTML/spacing and return unique ids per step text', () => {
|
|
1049
|
-
const text = '
|
|
977
|
+
const text = '<b>S</b><b>R</b> 0 0 0 1; SR0002; S R 0 0 0 3; SR0002; <b>SR</b>0004';
|
|
1050
978
|
const codes = resultDataProvider.extractRequirementCodesFromText(text);
|
|
1051
|
-
expect([...codes].sort()).toEqual(['
|
|
979
|
+
expect([...codes].sort()).toEqual(['SR0001', 'SR0002', 'SR0003', 'SR0004']);
|
|
980
|
+
});
|
|
981
|
+
it('should keep only clean SR tokens and ignore noisy version/VVRM fragments', () => {
|
|
982
|
+
const extract = (text) => [...resultDataProvider.extractRequirementCodesFromText(text)].sort();
|
|
983
|
+
expect(extract('SR12413; SR24513; SR25135 VVRM2425')).toEqual(['SR12413', 'SR24513']);
|
|
984
|
+
expect(extract('SR12413; SR12412; SR12413-V3.24')).toEqual(['SR12412', 'SR12413']);
|
|
985
|
+
expect(extract('SR12413; SR12412; SR12413 V3.24')).toEqual(['SR12412', 'SR12413']);
|
|
986
|
+
expect(extract('SR12413, SR12412, SR12413-V3.24')).toEqual(['SR12412', 'SR12413']);
|
|
987
|
+
const extractWithSuffix = (text) => [
|
|
988
|
+
...resultDataProvider.extractRequirementCodesFromExpectedText(text, true),
|
|
989
|
+
].sort();
|
|
990
|
+
expect(extractWithSuffix('SR0095-2,3; SR0100-1,2,3,4')).toEqual([
|
|
991
|
+
'SR0095-2',
|
|
992
|
+
'SR0095-3',
|
|
993
|
+
'SR0100-1',
|
|
994
|
+
'SR0100-2',
|
|
995
|
+
'SR0100-3',
|
|
996
|
+
'SR0100-4',
|
|
997
|
+
]);
|
|
1052
998
|
});
|
|
1053
999
|
it('should not backfill definition steps as not-run when a real run exists but has no action results', async () => {
|
|
1054
1000
|
jest.spyOn(resultDataProvider, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
@@ -1070,6 +1016,8 @@ describe('ResultDataProvider', () => {
|
|
|
1070
1016
|
{
|
|
1071
1017
|
workItemId: 7001,
|
|
1072
1018
|
requirementId: 'SR2001',
|
|
1019
|
+
baseKey: 'SR2001',
|
|
1020
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
1073
1021
|
title: 'Has run but no actions',
|
|
1074
1022
|
responsibility: 'ESUK',
|
|
1075
1023
|
linkedTestCaseIds: [101],
|
|
@@ -1097,12 +1045,10 @@ describe('ResultDataProvider', () => {
|
|
|
1097
1045
|
},
|
|
1098
1046
|
]);
|
|
1099
1047
|
const result = await resultDataProvider.getMewpL2CoverageFlatResults('123', mockProjectName, [1]);
|
|
1100
|
-
const row = result.rows.find((item) => item['
|
|
1048
|
+
const row = result.rows.find((item) => item['L2 REQ ID'] === 'SR2001');
|
|
1101
1049
|
expect(parseSpy).not.toHaveBeenCalled();
|
|
1102
1050
|
expect(row).toEqual(expect.objectContaining({
|
|
1103
|
-
'
|
|
1104
|
-
'Number of failed steps': 0,
|
|
1105
|
-
'Number of not run tests': 0,
|
|
1051
|
+
'L2 Run Status': 'Not Run',
|
|
1106
1052
|
}));
|
|
1107
1053
|
});
|
|
1108
1054
|
it('should not infer requirement id from unrelated SR text in non-identifier fields', () => {
|
|
@@ -1111,7 +1057,1241 @@ describe('ResultDataProvider', () => {
|
|
|
1111
1057
|
'Custom.CustomerId': 'customer id unknown',
|
|
1112
1058
|
'System.Title': 'Requirement without explicit SR code',
|
|
1113
1059
|
}, 4321);
|
|
1114
|
-
expect(requirementId).toBe('
|
|
1060
|
+
expect(requirementId).toBe('SR4321');
|
|
1061
|
+
});
|
|
1062
|
+
it('should derive responsibility from Custom.SAPWBS when present', () => {
|
|
1063
|
+
const responsibility = resultDataProvider.deriveMewpResponsibility({
|
|
1064
|
+
'Custom.SAPWBS': 'IL',
|
|
1065
|
+
'System.AreaPath': 'MEWP\\ESUK',
|
|
1066
|
+
});
|
|
1067
|
+
expect(responsibility).toBe('IL');
|
|
1068
|
+
});
|
|
1069
|
+
it('should derive responsibility from AreaPath suffix ATP/ATP\\\\ESUK when SAPWBS is empty', () => {
|
|
1070
|
+
const esuk = resultDataProvider.deriveMewpResponsibility({
|
|
1071
|
+
'Custom.SAPWBS': '',
|
|
1072
|
+
'System.AreaPath': 'MEWP\\Customer Requirements\\Level 2\\ATP\\ESUK',
|
|
1073
|
+
});
|
|
1074
|
+
const il = resultDataProvider.deriveMewpResponsibility({
|
|
1075
|
+
'Custom.SAPWBS': '',
|
|
1076
|
+
'System.AreaPath': 'MEWP\\Customer Requirements\\Level 2\\ATP',
|
|
1077
|
+
});
|
|
1078
|
+
expect(esuk).toBe('ESUK');
|
|
1079
|
+
expect(il).toBe('IL');
|
|
1080
|
+
});
|
|
1081
|
+
it('should emit additive rows for bugs and L3/L4 links without bug duplication', () => {
|
|
1082
|
+
const requirements = [
|
|
1083
|
+
{
|
|
1084
|
+
requirementId: 'SR5303',
|
|
1085
|
+
baseKey: 'SR5303',
|
|
1086
|
+
title: 'Req 5303',
|
|
1087
|
+
subSystem: 'Power',
|
|
1088
|
+
responsibility: 'ESUK',
|
|
1089
|
+
linkedTestCaseIds: [101],
|
|
1090
|
+
},
|
|
1091
|
+
];
|
|
1092
|
+
const requirementIndex = new Map([
|
|
1093
|
+
[
|
|
1094
|
+
'SR5303',
|
|
1095
|
+
new Map([
|
|
1096
|
+
[
|
|
1097
|
+
101,
|
|
1098
|
+
{
|
|
1099
|
+
passed: 0,
|
|
1100
|
+
failed: 1,
|
|
1101
|
+
notRun: 0,
|
|
1102
|
+
},
|
|
1103
|
+
],
|
|
1104
|
+
]),
|
|
1105
|
+
],
|
|
1106
|
+
]);
|
|
1107
|
+
const observedTestCaseIdsByRequirement = new Map([
|
|
1108
|
+
['SR5303', new Set([101])],
|
|
1109
|
+
]);
|
|
1110
|
+
const linkedRequirementsByTestCase = new Map([
|
|
1111
|
+
[
|
|
1112
|
+
101,
|
|
1113
|
+
{
|
|
1114
|
+
baseKeys: new Set(['SR5303']),
|
|
1115
|
+
fullCodes: new Set(['SR5303']),
|
|
1116
|
+
bugIds: new Set([10003, 20003]),
|
|
1117
|
+
},
|
|
1118
|
+
],
|
|
1119
|
+
]);
|
|
1120
|
+
const externalBugsByTestCase = new Map([
|
|
1121
|
+
[
|
|
1122
|
+
101,
|
|
1123
|
+
[
|
|
1124
|
+
{ id: 10003, title: 'Bug 10003', responsibility: 'Elisra', requirementBaseKey: 'SR5303' },
|
|
1125
|
+
{ id: 20003, title: 'Bug 20003', responsibility: 'ESUK', requirementBaseKey: 'SR5303' },
|
|
1126
|
+
],
|
|
1127
|
+
],
|
|
1128
|
+
]);
|
|
1129
|
+
const l3l4ByBaseKey = new Map([
|
|
1130
|
+
[
|
|
1131
|
+
'SR5303',
|
|
1132
|
+
[
|
|
1133
|
+
{ id: '9003', title: 'L3 9003', level: 'L3' },
|
|
1134
|
+
{ id: '9103', title: 'L4 9103', level: 'L4' },
|
|
1135
|
+
],
|
|
1136
|
+
],
|
|
1137
|
+
]);
|
|
1138
|
+
const rows = resultDataProvider.buildMewpCoverageRows(requirements, requirementIndex, observedTestCaseIdsByRequirement, linkedRequirementsByTestCase, l3l4ByBaseKey, externalBugsByTestCase);
|
|
1139
|
+
expect(rows).toHaveLength(4);
|
|
1140
|
+
expect(rows.map((row) => row['Bug ID'])).toEqual([10003, 20003, '', '']);
|
|
1141
|
+
expect(rows[2]).toEqual(expect.objectContaining({
|
|
1142
|
+
'L3 REQ ID': '9003',
|
|
1143
|
+
'L4 REQ ID': '',
|
|
1144
|
+
}));
|
|
1145
|
+
expect(rows[3]).toEqual(expect.objectContaining({
|
|
1146
|
+
'L3 REQ ID': '',
|
|
1147
|
+
'L4 REQ ID': '9103',
|
|
1148
|
+
}));
|
|
1149
|
+
});
|
|
1150
|
+
it('should not emit bug rows from ADO-linked bug ids when external bugs source is empty', () => {
|
|
1151
|
+
const requirements = [
|
|
1152
|
+
{
|
|
1153
|
+
requirementId: 'SR5304',
|
|
1154
|
+
baseKey: 'SR5304',
|
|
1155
|
+
title: 'Req 5304',
|
|
1156
|
+
subSystem: 'Power',
|
|
1157
|
+
responsibility: 'ESUK',
|
|
1158
|
+
linkedTestCaseIds: [101],
|
|
1159
|
+
},
|
|
1160
|
+
];
|
|
1161
|
+
const requirementIndex = new Map([
|
|
1162
|
+
[
|
|
1163
|
+
'SR5304',
|
|
1164
|
+
new Map([
|
|
1165
|
+
[
|
|
1166
|
+
101,
|
|
1167
|
+
{
|
|
1168
|
+
passed: 0,
|
|
1169
|
+
failed: 1,
|
|
1170
|
+
notRun: 0,
|
|
1171
|
+
},
|
|
1172
|
+
],
|
|
1173
|
+
]),
|
|
1174
|
+
],
|
|
1175
|
+
]);
|
|
1176
|
+
const observedTestCaseIdsByRequirement = new Map([
|
|
1177
|
+
['SR5304', new Set([101])],
|
|
1178
|
+
]);
|
|
1179
|
+
const linkedRequirementsByTestCase = new Map([
|
|
1180
|
+
[
|
|
1181
|
+
101,
|
|
1182
|
+
{
|
|
1183
|
+
baseKeys: new Set(['SR5304']),
|
|
1184
|
+
fullCodes: new Set(['SR5304']),
|
|
1185
|
+
bugIds: new Set([55555]), // must be ignored in MEWP coverage mode
|
|
1186
|
+
},
|
|
1187
|
+
],
|
|
1188
|
+
]);
|
|
1189
|
+
const rows = resultDataProvider.buildMewpCoverageRows(requirements, requirementIndex, observedTestCaseIdsByRequirement, linkedRequirementsByTestCase, new Map(), new Map());
|
|
1190
|
+
expect(rows).toHaveLength(1);
|
|
1191
|
+
expect(rows[0]['L2 Run Status']).toBe('Fail');
|
|
1192
|
+
expect(rows[0]['Bug ID']).toBe('');
|
|
1193
|
+
expect(rows[0]['Bug Title']).toBe('');
|
|
1194
|
+
expect(rows[0]['Bug Responsibility']).toBe('');
|
|
1195
|
+
});
|
|
1196
|
+
it('should fallback bug responsibility from requirement when external bug row has unknown responsibility', () => {
|
|
1197
|
+
const requirements = [
|
|
1198
|
+
{
|
|
1199
|
+
requirementId: 'SR5305',
|
|
1200
|
+
baseKey: 'SR5305',
|
|
1201
|
+
title: 'Req 5305',
|
|
1202
|
+
subSystem: 'Auth',
|
|
1203
|
+
responsibility: 'IL',
|
|
1204
|
+
linkedTestCaseIds: [202],
|
|
1205
|
+
},
|
|
1206
|
+
];
|
|
1207
|
+
const requirementIndex = new Map([
|
|
1208
|
+
[
|
|
1209
|
+
'SR5305',
|
|
1210
|
+
new Map([
|
|
1211
|
+
[
|
|
1212
|
+
202,
|
|
1213
|
+
{
|
|
1214
|
+
passed: 0,
|
|
1215
|
+
failed: 1,
|
|
1216
|
+
notRun: 0,
|
|
1217
|
+
},
|
|
1218
|
+
],
|
|
1219
|
+
]),
|
|
1220
|
+
],
|
|
1221
|
+
]);
|
|
1222
|
+
const observedTestCaseIdsByRequirement = new Map([
|
|
1223
|
+
['SR5305', new Set([202])],
|
|
1224
|
+
]);
|
|
1225
|
+
const linkedRequirementsByTestCase = new Map([
|
|
1226
|
+
[
|
|
1227
|
+
202,
|
|
1228
|
+
{
|
|
1229
|
+
baseKeys: new Set(['SR5305']),
|
|
1230
|
+
fullCodes: new Set(['SR5305']),
|
|
1231
|
+
bugIds: new Set(),
|
|
1232
|
+
},
|
|
1233
|
+
],
|
|
1234
|
+
]);
|
|
1235
|
+
const externalBugsByTestCase = new Map([
|
|
1236
|
+
[
|
|
1237
|
+
202,
|
|
1238
|
+
[
|
|
1239
|
+
{
|
|
1240
|
+
id: 99001,
|
|
1241
|
+
title: 'External bug without SAPWBS',
|
|
1242
|
+
responsibility: 'Unknown',
|
|
1243
|
+
requirementBaseKey: 'SR5305',
|
|
1244
|
+
},
|
|
1245
|
+
],
|
|
1246
|
+
],
|
|
1247
|
+
]);
|
|
1248
|
+
const rows = resultDataProvider.buildMewpCoverageRows(requirements, requirementIndex, observedTestCaseIdsByRequirement, linkedRequirementsByTestCase, new Map(), externalBugsByTestCase);
|
|
1249
|
+
expect(rows).toHaveLength(1);
|
|
1250
|
+
expect(rows[0]['Bug ID']).toBe(99001);
|
|
1251
|
+
expect(rows[0]['Bug Responsibility']).toBe('Elisra');
|
|
1252
|
+
});
|
|
1253
|
+
});
|
|
1254
|
+
describe('getMewpInternalValidationFlatResults', () => {
|
|
1255
|
+
it('should skip test cases with no in-scope expected requirements and no linked requirements', async () => {
|
|
1256
|
+
jest.spyOn(resultDataProvider, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
1257
|
+
jest.spyOn(resultDataProvider, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
|
|
1258
|
+
jest.spyOn(resultDataProvider, 'fetchTestData').mockResolvedValueOnce([
|
|
1259
|
+
{
|
|
1260
|
+
testPointsItems: [
|
|
1261
|
+
{ testCaseId: 101, testCaseName: 'TC 101' },
|
|
1262
|
+
{ testCaseId: 102, testCaseName: 'TC 102' },
|
|
1263
|
+
],
|
|
1264
|
+
testCasesItems: [
|
|
1265
|
+
{
|
|
1266
|
+
workItem: {
|
|
1267
|
+
id: 101,
|
|
1268
|
+
workItemFields: [{ key: 'Steps', value: '<steps></steps>' }],
|
|
1269
|
+
},
|
|
1270
|
+
},
|
|
1271
|
+
{
|
|
1272
|
+
workItem: {
|
|
1273
|
+
id: 102,
|
|
1274
|
+
workItemFields: [{ key: 'Steps', value: '<steps></steps>' }],
|
|
1275
|
+
},
|
|
1276
|
+
},
|
|
1277
|
+
],
|
|
1278
|
+
},
|
|
1279
|
+
]);
|
|
1280
|
+
jest.spyOn(resultDataProvider, 'fetchMewpL2Requirements').mockResolvedValueOnce([
|
|
1281
|
+
{
|
|
1282
|
+
workItemId: 5001,
|
|
1283
|
+
requirementId: 'SR0001',
|
|
1284
|
+
baseKey: 'SR0001',
|
|
1285
|
+
title: 'Req 1',
|
|
1286
|
+
responsibility: 'ESUK',
|
|
1287
|
+
linkedTestCaseIds: [101],
|
|
1288
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
1289
|
+
},
|
|
1290
|
+
]);
|
|
1291
|
+
jest.spyOn(resultDataProvider, 'buildLinkedRequirementsByTestCase').mockResolvedValueOnce(new Map([[101, { baseKeys: new Set(['SR0001']), fullCodes: new Set(['SR0001']) }]]));
|
|
1292
|
+
jest
|
|
1293
|
+
.spyOn(resultDataProvider.testStepParserHelper, 'parseTestSteps')
|
|
1294
|
+
.mockResolvedValueOnce([
|
|
1295
|
+
{
|
|
1296
|
+
stepId: '1',
|
|
1297
|
+
stepPosition: '1',
|
|
1298
|
+
action: '',
|
|
1299
|
+
expected: 'SR0001',
|
|
1300
|
+
isSharedStepTitle: false,
|
|
1301
|
+
},
|
|
1302
|
+
])
|
|
1303
|
+
.mockResolvedValueOnce([
|
|
1304
|
+
{
|
|
1305
|
+
stepId: '1',
|
|
1306
|
+
stepPosition: '1',
|
|
1307
|
+
action: '',
|
|
1308
|
+
expected: '',
|
|
1309
|
+
isSharedStepTitle: false,
|
|
1310
|
+
},
|
|
1311
|
+
]);
|
|
1312
|
+
const result = await resultDataProvider.getMewpInternalValidationFlatResults('123', mockProjectName, [1]);
|
|
1313
|
+
expect(result.rows).toHaveLength(2);
|
|
1314
|
+
expect(result.rows).toEqual(expect.arrayContaining([
|
|
1315
|
+
expect.objectContaining({
|
|
1316
|
+
'Test Case ID': 101,
|
|
1317
|
+
'Validation Status': 'Pass',
|
|
1318
|
+
'Mentioned but Not Linked': '',
|
|
1319
|
+
'Linked but Not Mentioned': '',
|
|
1320
|
+
}),
|
|
1321
|
+
expect.objectContaining({
|
|
1322
|
+
'Test Case ID': 102,
|
|
1323
|
+
'Validation Status': 'Pass',
|
|
1324
|
+
'Mentioned but Not Linked': '',
|
|
1325
|
+
'Linked but Not Mentioned': '',
|
|
1326
|
+
}),
|
|
1327
|
+
]));
|
|
1328
|
+
});
|
|
1329
|
+
it('should ignore non-L2 SR mentions and still report only real L2 discrepancies', async () => {
|
|
1330
|
+
jest.spyOn(resultDataProvider, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
1331
|
+
jest.spyOn(resultDataProvider, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
|
|
1332
|
+
jest.spyOn(resultDataProvider, 'fetchTestData').mockResolvedValueOnce([
|
|
1333
|
+
{
|
|
1334
|
+
testPointsItems: [{ testCaseId: 101, testCaseName: 'TC 101' }],
|
|
1335
|
+
testCasesItems: [
|
|
1336
|
+
{
|
|
1337
|
+
workItem: {
|
|
1338
|
+
id: 101,
|
|
1339
|
+
workItemFields: [{ key: 'Steps', value: '<steps></steps>' }],
|
|
1340
|
+
},
|
|
1341
|
+
},
|
|
1342
|
+
],
|
|
1343
|
+
},
|
|
1344
|
+
]);
|
|
1345
|
+
jest.spyOn(resultDataProvider, 'fetchMewpL2Requirements').mockResolvedValueOnce([
|
|
1346
|
+
{
|
|
1347
|
+
workItemId: 5001,
|
|
1348
|
+
requirementId: 'SR0001',
|
|
1349
|
+
baseKey: 'SR0001',
|
|
1350
|
+
title: 'Req 1',
|
|
1351
|
+
responsibility: 'ESUK',
|
|
1352
|
+
linkedTestCaseIds: [101],
|
|
1353
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
1354
|
+
},
|
|
1355
|
+
]);
|
|
1356
|
+
jest.spyOn(resultDataProvider, 'buildLinkedRequirementsByTestCase').mockResolvedValueOnce(new Map());
|
|
1357
|
+
jest.spyOn(resultDataProvider.testStepParserHelper, 'parseTestSteps').mockResolvedValueOnce([
|
|
1358
|
+
{
|
|
1359
|
+
stepId: '1',
|
|
1360
|
+
stepPosition: '1',
|
|
1361
|
+
action: '',
|
|
1362
|
+
expected: 'SR0001; SR9999',
|
|
1363
|
+
isSharedStepTitle: false,
|
|
1364
|
+
},
|
|
1365
|
+
]);
|
|
1366
|
+
const result = await resultDataProvider.getMewpInternalValidationFlatResults('123', mockProjectName, [1]);
|
|
1367
|
+
expect(result.rows).toHaveLength(1);
|
|
1368
|
+
expect(result.rows[0]).toEqual(expect.objectContaining({
|
|
1369
|
+
'Test Case ID': 101,
|
|
1370
|
+
'Mentioned but Not Linked': 'Step 1: SR0001',
|
|
1371
|
+
'Linked but Not Mentioned': '',
|
|
1372
|
+
'Validation Status': 'Fail',
|
|
1373
|
+
}));
|
|
1374
|
+
});
|
|
1375
|
+
it('should emit Direction A rows with step context for missing sibling links', async () => {
|
|
1376
|
+
jest.spyOn(resultDataProvider, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
1377
|
+
jest.spyOn(resultDataProvider, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
|
|
1378
|
+
jest.spyOn(resultDataProvider, 'fetchTestData').mockResolvedValueOnce([
|
|
1379
|
+
{
|
|
1380
|
+
testPointsItems: [{ testCaseId: 101, testCaseName: 'TC 101' }],
|
|
1381
|
+
testCasesItems: [
|
|
1382
|
+
{
|
|
1383
|
+
workItem: {
|
|
1384
|
+
id: 101,
|
|
1385
|
+
workItemFields: [{ key: 'Steps', value: '<steps></steps>' }],
|
|
1386
|
+
},
|
|
1387
|
+
},
|
|
1388
|
+
],
|
|
1389
|
+
},
|
|
1390
|
+
]);
|
|
1391
|
+
jest.spyOn(resultDataProvider, 'fetchMewpL2Requirements').mockResolvedValueOnce([
|
|
1392
|
+
{
|
|
1393
|
+
workItemId: 5001,
|
|
1394
|
+
requirementId: 'SR0001',
|
|
1395
|
+
baseKey: 'SR0001',
|
|
1396
|
+
title: 'Req parent',
|
|
1397
|
+
responsibility: 'ESUK',
|
|
1398
|
+
linkedTestCaseIds: [101],
|
|
1399
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
1400
|
+
},
|
|
1401
|
+
{
|
|
1402
|
+
workItemId: 5002,
|
|
1403
|
+
requirementId: 'SR0001-1',
|
|
1404
|
+
baseKey: 'SR0001',
|
|
1405
|
+
title: 'Req child',
|
|
1406
|
+
responsibility: 'ESUK',
|
|
1407
|
+
linkedTestCaseIds: [],
|
|
1408
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2\\MOP',
|
|
1409
|
+
},
|
|
1410
|
+
]);
|
|
1411
|
+
jest.spyOn(resultDataProvider, 'buildLinkedRequirementsByTestCase').mockResolvedValueOnce(new Map([[101, { baseKeys: new Set(['SR0001']), fullCodes: new Set(['SR0001']) }]]));
|
|
1412
|
+
jest.spyOn(resultDataProvider.testStepParserHelper, 'parseTestSteps').mockResolvedValueOnce([
|
|
1413
|
+
{
|
|
1414
|
+
stepId: '2',
|
|
1415
|
+
stepPosition: '2',
|
|
1416
|
+
action: '',
|
|
1417
|
+
expected: 'SR0001; SR0002',
|
|
1418
|
+
isSharedStepTitle: false,
|
|
1419
|
+
},
|
|
1420
|
+
]);
|
|
1421
|
+
const result = await resultDataProvider.getMewpInternalValidationFlatResults('123', mockProjectName, [1]);
|
|
1422
|
+
expect(result.rows).toEqual(expect.arrayContaining([
|
|
1423
|
+
expect.objectContaining({
|
|
1424
|
+
'Test Case ID': 101,
|
|
1425
|
+
'Mentioned but Not Linked': expect.stringContaining('Step 2: SR0001-1'),
|
|
1426
|
+
'Validation Status': 'Fail',
|
|
1427
|
+
}),
|
|
1428
|
+
]));
|
|
1429
|
+
});
|
|
1430
|
+
it('should not duplicate Direction A discrepancy when same requirement is repeated in multiple steps', async () => {
|
|
1431
|
+
jest.spyOn(resultDataProvider, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
1432
|
+
jest.spyOn(resultDataProvider, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
|
|
1433
|
+
jest.spyOn(resultDataProvider, 'fetchTestData').mockResolvedValueOnce([
|
|
1434
|
+
{
|
|
1435
|
+
testPointsItems: [{ testCaseId: 101, testCaseName: 'TC 101' }],
|
|
1436
|
+
testCasesItems: [
|
|
1437
|
+
{
|
|
1438
|
+
workItem: {
|
|
1439
|
+
id: 101,
|
|
1440
|
+
workItemFields: [{ key: 'Steps', value: '<steps></steps>' }],
|
|
1441
|
+
},
|
|
1442
|
+
},
|
|
1443
|
+
],
|
|
1444
|
+
},
|
|
1445
|
+
]);
|
|
1446
|
+
jest.spyOn(resultDataProvider, 'fetchMewpL2Requirements').mockResolvedValueOnce([
|
|
1447
|
+
{
|
|
1448
|
+
workItemId: 5001,
|
|
1449
|
+
requirementId: 'SR0001',
|
|
1450
|
+
baseKey: 'SR0001',
|
|
1451
|
+
title: 'Req 1',
|
|
1452
|
+
responsibility: 'ESUK',
|
|
1453
|
+
linkedTestCaseIds: [],
|
|
1454
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
1455
|
+
},
|
|
1456
|
+
]);
|
|
1457
|
+
jest
|
|
1458
|
+
.spyOn(resultDataProvider, 'buildLinkedRequirementsByTestCase')
|
|
1459
|
+
.mockResolvedValueOnce(new Map());
|
|
1460
|
+
jest.spyOn(resultDataProvider.testStepParserHelper, 'parseTestSteps').mockResolvedValueOnce([
|
|
1461
|
+
{
|
|
1462
|
+
stepId: '1',
|
|
1463
|
+
stepPosition: '1',
|
|
1464
|
+
action: '',
|
|
1465
|
+
expected: 'SR0001',
|
|
1466
|
+
isSharedStepTitle: false,
|
|
1467
|
+
},
|
|
1468
|
+
{
|
|
1469
|
+
stepId: '2',
|
|
1470
|
+
stepPosition: '2',
|
|
1471
|
+
action: '',
|
|
1472
|
+
expected: 'SR0001',
|
|
1473
|
+
isSharedStepTitle: false,
|
|
1474
|
+
},
|
|
1475
|
+
]);
|
|
1476
|
+
const result = await resultDataProvider.getMewpInternalValidationFlatResults('123', mockProjectName, [1]);
|
|
1477
|
+
expect(result.rows).toHaveLength(1);
|
|
1478
|
+
expect(result.rows[0]).toEqual(expect.objectContaining({
|
|
1479
|
+
'Test Case ID': 101,
|
|
1480
|
+
'Mentioned but Not Linked': 'Step 1: SR0001',
|
|
1481
|
+
'Linked but Not Mentioned': '',
|
|
1482
|
+
'Validation Status': 'Fail',
|
|
1483
|
+
}));
|
|
1484
|
+
});
|
|
1485
|
+
it('should produce one detailed row per test case with correct bidirectional discrepancies', async () => {
|
|
1486
|
+
const mockDetailedStepsByTestCase = new Map([
|
|
1487
|
+
[
|
|
1488
|
+
201,
|
|
1489
|
+
[
|
|
1490
|
+
{
|
|
1491
|
+
stepId: '1',
|
|
1492
|
+
stepPosition: '1',
|
|
1493
|
+
action: 'Validate parent SR0511 and SR0095 siblings',
|
|
1494
|
+
expected: '<b>sr0511</b>; SR0095-2,3; VVRM-05',
|
|
1495
|
+
isSharedStepTitle: false,
|
|
1496
|
+
},
|
|
1497
|
+
{
|
|
1498
|
+
stepId: '2',
|
|
1499
|
+
stepPosition: '2',
|
|
1500
|
+
action: 'Noisy requirement-like token should be ignored',
|
|
1501
|
+
expected: 'SR0511-V3.24',
|
|
1502
|
+
isSharedStepTitle: false,
|
|
1503
|
+
},
|
|
1504
|
+
{
|
|
1505
|
+
stepId: '3',
|
|
1506
|
+
stepPosition: '3',
|
|
1507
|
+
action: 'Regression note',
|
|
1508
|
+
expected: 'Verification note only',
|
|
1509
|
+
isSharedStepTitle: false,
|
|
1510
|
+
},
|
|
1511
|
+
],
|
|
1512
|
+
],
|
|
1513
|
+
[
|
|
1514
|
+
202,
|
|
1515
|
+
[
|
|
1516
|
+
{
|
|
1517
|
+
stepId: '1',
|
|
1518
|
+
stepPosition: '1',
|
|
1519
|
+
action: 'Linked SR0200 exists but is not cleanly mentioned in expected result',
|
|
1520
|
+
expected: 'VVRM-22; SR0200 V3.1',
|
|
1521
|
+
isSharedStepTitle: false,
|
|
1522
|
+
},
|
|
1523
|
+
{
|
|
1524
|
+
stepId: '2',
|
|
1525
|
+
stepPosition: '2',
|
|
1526
|
+
action: 'Execution notes',
|
|
1527
|
+
expected: 'Notes without SR requirement token',
|
|
1528
|
+
isSharedStepTitle: false,
|
|
1529
|
+
},
|
|
1530
|
+
],
|
|
1531
|
+
],
|
|
1532
|
+
[
|
|
1533
|
+
203,
|
|
1534
|
+
[
|
|
1535
|
+
{
|
|
1536
|
+
stepId: '1',
|
|
1537
|
+
stepPosition: '1',
|
|
1538
|
+
action: 'Primary requirement validation for SR0100-1',
|
|
1539
|
+
expected: '<i>SR0100-1</i>',
|
|
1540
|
+
isSharedStepTitle: false,
|
|
1541
|
+
},
|
|
1542
|
+
{
|
|
1543
|
+
stepId: '3',
|
|
1544
|
+
stepPosition: '3',
|
|
1545
|
+
action: 'Repeated mention should not create duplicate mismatch',
|
|
1546
|
+
expected: 'SR0100-1; SR0100-1',
|
|
1547
|
+
isSharedStepTitle: false,
|
|
1548
|
+
},
|
|
1549
|
+
],
|
|
1550
|
+
],
|
|
1551
|
+
]);
|
|
1552
|
+
const mockLinkedRequirementsByTestCase = new Map([
|
|
1553
|
+
[
|
|
1554
|
+
201,
|
|
1555
|
+
{
|
|
1556
|
+
baseKeys: new Set(['SR0511', 'SR0095', 'SR8888']),
|
|
1557
|
+
fullCodes: new Set(['SR0511', 'SR0095-2', 'SR8888']),
|
|
1558
|
+
},
|
|
1559
|
+
],
|
|
1560
|
+
[
|
|
1561
|
+
202,
|
|
1562
|
+
{
|
|
1563
|
+
baseKeys: new Set(['SR0200']),
|
|
1564
|
+
fullCodes: new Set(['SR0200']),
|
|
1565
|
+
},
|
|
1566
|
+
],
|
|
1567
|
+
[
|
|
1568
|
+
203,
|
|
1569
|
+
{
|
|
1570
|
+
baseKeys: new Set(['SR0100']),
|
|
1571
|
+
fullCodes: new Set(['SR0100-1']),
|
|
1572
|
+
},
|
|
1573
|
+
],
|
|
1574
|
+
]);
|
|
1575
|
+
jest.spyOn(resultDataProvider, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
1576
|
+
jest.spyOn(resultDataProvider, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
|
|
1577
|
+
jest.spyOn(resultDataProvider, 'fetchTestData').mockResolvedValueOnce([
|
|
1578
|
+
{
|
|
1579
|
+
testPointsItems: [
|
|
1580
|
+
{ testCaseId: 201, testCaseName: 'TC 201 - Mixed discrepancies' },
|
|
1581
|
+
{ testCaseId: 202, testCaseName: 'TC 202 - Link only' },
|
|
1582
|
+
{ testCaseId: 203, testCaseName: 'TC 203 - Fully valid' },
|
|
1583
|
+
],
|
|
1584
|
+
testCasesItems: [
|
|
1585
|
+
{
|
|
1586
|
+
workItem: {
|
|
1587
|
+
id: 201,
|
|
1588
|
+
workItemFields: [{ key: 'Steps', value: '<steps id="mock-steps-tc-201"></steps>' }],
|
|
1589
|
+
},
|
|
1590
|
+
},
|
|
1591
|
+
{
|
|
1592
|
+
workItem: {
|
|
1593
|
+
id: 202,
|
|
1594
|
+
workItemFields: [{ key: 'Steps', value: '<steps id="mock-steps-tc-202"></steps>' }],
|
|
1595
|
+
},
|
|
1596
|
+
},
|
|
1597
|
+
{
|
|
1598
|
+
workItem: {
|
|
1599
|
+
id: 203,
|
|
1600
|
+
workItemFields: [{ key: 'Steps', value: '<steps id="mock-steps-tc-203"></steps>' }],
|
|
1601
|
+
},
|
|
1602
|
+
},
|
|
1603
|
+
],
|
|
1604
|
+
},
|
|
1605
|
+
]);
|
|
1606
|
+
jest.spyOn(resultDataProvider, 'fetchMewpL2Requirements').mockResolvedValueOnce([
|
|
1607
|
+
{
|
|
1608
|
+
workItemId: 6001,
|
|
1609
|
+
requirementId: 'SR0511',
|
|
1610
|
+
baseKey: 'SR0511',
|
|
1611
|
+
title: 'Parent 0511',
|
|
1612
|
+
responsibility: 'ESUK',
|
|
1613
|
+
linkedTestCaseIds: [201],
|
|
1614
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
1615
|
+
},
|
|
1616
|
+
{
|
|
1617
|
+
workItemId: 6002,
|
|
1618
|
+
requirementId: 'SR0511-1',
|
|
1619
|
+
baseKey: 'SR0511',
|
|
1620
|
+
title: 'Child 0511-1',
|
|
1621
|
+
responsibility: 'ESUK',
|
|
1622
|
+
linkedTestCaseIds: [],
|
|
1623
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2\\MOP',
|
|
1624
|
+
},
|
|
1625
|
+
{
|
|
1626
|
+
workItemId: 6003,
|
|
1627
|
+
requirementId: 'SR0511-2',
|
|
1628
|
+
baseKey: 'SR0511',
|
|
1629
|
+
title: 'Child 0511-2',
|
|
1630
|
+
responsibility: 'ESUK',
|
|
1631
|
+
linkedTestCaseIds: [],
|
|
1632
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2\\MOP',
|
|
1633
|
+
},
|
|
1634
|
+
{
|
|
1635
|
+
workItemId: 6004,
|
|
1636
|
+
requirementId: 'SR0095-2',
|
|
1637
|
+
baseKey: 'SR0095',
|
|
1638
|
+
title: 'SR0095 child 2',
|
|
1639
|
+
responsibility: 'ESUK',
|
|
1640
|
+
linkedTestCaseIds: [],
|
|
1641
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2\\MOP',
|
|
1642
|
+
},
|
|
1643
|
+
{
|
|
1644
|
+
workItemId: 6005,
|
|
1645
|
+
requirementId: 'SR0095-3',
|
|
1646
|
+
baseKey: 'SR0095',
|
|
1647
|
+
title: 'SR0095 child 3',
|
|
1648
|
+
responsibility: 'ESUK',
|
|
1649
|
+
linkedTestCaseIds: [],
|
|
1650
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2\\MOP',
|
|
1651
|
+
},
|
|
1652
|
+
{
|
|
1653
|
+
workItemId: 6006,
|
|
1654
|
+
requirementId: 'SR0200',
|
|
1655
|
+
baseKey: 'SR0200',
|
|
1656
|
+
title: 'SR0200 standalone',
|
|
1657
|
+
responsibility: 'ESUK',
|
|
1658
|
+
linkedTestCaseIds: [202],
|
|
1659
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
1660
|
+
},
|
|
1661
|
+
{
|
|
1662
|
+
workItemId: 6007,
|
|
1663
|
+
requirementId: 'SR0100-1',
|
|
1664
|
+
baseKey: 'SR0100',
|
|
1665
|
+
title: 'SR0100 child 1',
|
|
1666
|
+
responsibility: 'ESUK',
|
|
1667
|
+
linkedTestCaseIds: [203],
|
|
1668
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2\\MOP',
|
|
1669
|
+
},
|
|
1670
|
+
]);
|
|
1671
|
+
jest
|
|
1672
|
+
.spyOn(resultDataProvider, 'buildLinkedRequirementsByTestCase')
|
|
1673
|
+
.mockResolvedValueOnce(mockLinkedRequirementsByTestCase);
|
|
1674
|
+
jest
|
|
1675
|
+
.spyOn(resultDataProvider.testStepParserHelper, 'parseTestSteps')
|
|
1676
|
+
.mockImplementation(async (...args) => {
|
|
1677
|
+
const stepsXml = String((args === null || args === void 0 ? void 0 : args[0]) || '');
|
|
1678
|
+
const testCaseMatch = /mock-steps-tc-(\d+)/i.exec(stepsXml);
|
|
1679
|
+
const testCaseId = Number((testCaseMatch === null || testCaseMatch === void 0 ? void 0 : testCaseMatch[1]) || 0);
|
|
1680
|
+
return mockDetailedStepsByTestCase.get(testCaseId) || [];
|
|
1681
|
+
});
|
|
1682
|
+
const result = await resultDataProvider.getMewpInternalValidationFlatResults('123', mockProjectName, [1]);
|
|
1683
|
+
expect(result.rows).toHaveLength(3);
|
|
1684
|
+
const byTestCase = new Map(result.rows.map((row) => [row['Test Case ID'], row]));
|
|
1685
|
+
expect(new Set(result.rows.map((row) => row['Test Case ID']))).toEqual(new Set([201, 202, 203]));
|
|
1686
|
+
expect(byTestCase.get(201)).toEqual(expect.objectContaining({
|
|
1687
|
+
'Test Case Title': 'TC 201 - Mixed discrepancies',
|
|
1688
|
+
'Mentioned but Not Linked': 'Step 1: SR0095-3, SR0511-1, SR0511-2',
|
|
1689
|
+
'Linked but Not Mentioned': 'SR8888',
|
|
1690
|
+
'Validation Status': 'Fail',
|
|
1691
|
+
}));
|
|
1692
|
+
expect(String(byTestCase.get(201)['Mentioned but Not Linked'] || '')).not.toContain('VVRM');
|
|
1693
|
+
expect(byTestCase.get(202)).toEqual(expect.objectContaining({
|
|
1694
|
+
'Test Case Title': 'TC 202 - Link only',
|
|
1695
|
+
'Mentioned but Not Linked': '',
|
|
1696
|
+
'Linked but Not Mentioned': 'SR0200',
|
|
1697
|
+
'Validation Status': 'Fail',
|
|
1698
|
+
}));
|
|
1699
|
+
expect(byTestCase.get(203)).toEqual(expect.objectContaining({
|
|
1700
|
+
'Test Case Title': 'TC 203 - Fully valid',
|
|
1701
|
+
'Mentioned but Not Linked': '',
|
|
1702
|
+
'Linked but Not Mentioned': '',
|
|
1703
|
+
'Validation Status': 'Pass',
|
|
1704
|
+
}));
|
|
1705
|
+
expect(resultDataProvider.testStepParserHelper.parseTestSteps).toHaveBeenCalledTimes(3);
|
|
1706
|
+
const parseCalls = resultDataProvider.testStepParserHelper.parseTestSteps.mock.calls;
|
|
1707
|
+
const parsedCaseIds = parseCalls
|
|
1708
|
+
.map(([xml]) => {
|
|
1709
|
+
const match = /mock-steps-tc-(\d+)/i.exec(String(xml || ''));
|
|
1710
|
+
return Number((match === null || match === void 0 ? void 0 : match[1]) || 0);
|
|
1711
|
+
})
|
|
1712
|
+
.filter((id) => Number.isFinite(id) && id > 0);
|
|
1713
|
+
expect(new Set(parsedCaseIds)).toEqual(new Set([201, 202, 203]));
|
|
1714
|
+
});
|
|
1715
|
+
it('should parse TC-0042 mixed expected text and keep only valid SR requirement codes', async () => {
|
|
1716
|
+
jest.spyOn(resultDataProvider, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
1717
|
+
jest.spyOn(resultDataProvider, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
|
|
1718
|
+
jest.spyOn(resultDataProvider, 'fetchTestData').mockResolvedValueOnce([
|
|
1719
|
+
{
|
|
1720
|
+
testPointsItems: [{ testCaseId: 42, testCaseName: 'TC-0042' }],
|
|
1721
|
+
testCasesItems: [
|
|
1722
|
+
{
|
|
1723
|
+
workItem: {
|
|
1724
|
+
id: 42,
|
|
1725
|
+
workItemFields: [{ key: 'Steps', value: '<steps id="mock-steps-tc-0042"></steps>' }],
|
|
1726
|
+
},
|
|
1727
|
+
},
|
|
1728
|
+
],
|
|
1729
|
+
},
|
|
1730
|
+
]);
|
|
1731
|
+
jest.spyOn(resultDataProvider, 'fetchMewpL2Requirements').mockResolvedValueOnce([
|
|
1732
|
+
{
|
|
1733
|
+
workItemId: 7001,
|
|
1734
|
+
requirementId: 'SR0036',
|
|
1735
|
+
baseKey: 'SR0036',
|
|
1736
|
+
title: 'SR0036',
|
|
1737
|
+
responsibility: 'ESUK',
|
|
1738
|
+
linkedTestCaseIds: [],
|
|
1739
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
1740
|
+
},
|
|
1741
|
+
{
|
|
1742
|
+
workItemId: 7002,
|
|
1743
|
+
requirementId: 'SR0215',
|
|
1744
|
+
baseKey: 'SR0215',
|
|
1745
|
+
title: 'SR0215',
|
|
1746
|
+
responsibility: 'ESUK',
|
|
1747
|
+
linkedTestCaseIds: [],
|
|
1748
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
1749
|
+
},
|
|
1750
|
+
{
|
|
1751
|
+
workItemId: 7003,
|
|
1752
|
+
requirementId: 'SR0539',
|
|
1753
|
+
baseKey: 'SR0539',
|
|
1754
|
+
title: 'SR0539',
|
|
1755
|
+
responsibility: 'ESUK',
|
|
1756
|
+
linkedTestCaseIds: [],
|
|
1757
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
1758
|
+
},
|
|
1759
|
+
{
|
|
1760
|
+
workItemId: 7004,
|
|
1761
|
+
requirementId: 'SR0348',
|
|
1762
|
+
baseKey: 'SR0348',
|
|
1763
|
+
title: 'SR0348',
|
|
1764
|
+
responsibility: 'ESUK',
|
|
1765
|
+
linkedTestCaseIds: [],
|
|
1766
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
1767
|
+
},
|
|
1768
|
+
{
|
|
1769
|
+
workItemId: 7005,
|
|
1770
|
+
requirementId: 'SR0027',
|
|
1771
|
+
baseKey: 'SR0027',
|
|
1772
|
+
title: 'SR0027',
|
|
1773
|
+
responsibility: 'ESUK',
|
|
1774
|
+
linkedTestCaseIds: [],
|
|
1775
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
1776
|
+
},
|
|
1777
|
+
{
|
|
1778
|
+
workItemId: 7006,
|
|
1779
|
+
requirementId: 'SR0041',
|
|
1780
|
+
baseKey: 'SR0041',
|
|
1781
|
+
title: 'SR0041',
|
|
1782
|
+
responsibility: 'ESUK',
|
|
1783
|
+
linkedTestCaseIds: [],
|
|
1784
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
1785
|
+
},
|
|
1786
|
+
{
|
|
1787
|
+
workItemId: 7007,
|
|
1788
|
+
requirementId: 'SR0550',
|
|
1789
|
+
baseKey: 'SR0550',
|
|
1790
|
+
title: 'SR0550',
|
|
1791
|
+
responsibility: 'ESUK',
|
|
1792
|
+
linkedTestCaseIds: [],
|
|
1793
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
1794
|
+
},
|
|
1795
|
+
{
|
|
1796
|
+
workItemId: 7008,
|
|
1797
|
+
requirementId: 'SR0550-2',
|
|
1798
|
+
baseKey: 'SR0550',
|
|
1799
|
+
title: 'SR0550-2',
|
|
1800
|
+
responsibility: 'ESUK',
|
|
1801
|
+
linkedTestCaseIds: [],
|
|
1802
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2\\MOP',
|
|
1803
|
+
},
|
|
1804
|
+
{
|
|
1805
|
+
workItemId: 7009,
|
|
1806
|
+
requirementId: 'SR0817',
|
|
1807
|
+
baseKey: 'SR0817',
|
|
1808
|
+
title: 'SR0817',
|
|
1809
|
+
responsibility: 'ESUK',
|
|
1810
|
+
linkedTestCaseIds: [],
|
|
1811
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
1812
|
+
},
|
|
1813
|
+
{
|
|
1814
|
+
workItemId: 7010,
|
|
1815
|
+
requirementId: 'SR0818',
|
|
1816
|
+
baseKey: 'SR0818',
|
|
1817
|
+
title: 'SR0818',
|
|
1818
|
+
responsibility: 'ESUK',
|
|
1819
|
+
linkedTestCaseIds: [],
|
|
1820
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
1821
|
+
},
|
|
1822
|
+
{
|
|
1823
|
+
workItemId: 7011,
|
|
1824
|
+
requirementId: 'SR0859',
|
|
1825
|
+
baseKey: 'SR0859',
|
|
1826
|
+
title: 'SR0859',
|
|
1827
|
+
responsibility: 'ESUK',
|
|
1828
|
+
linkedTestCaseIds: [],
|
|
1829
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
1830
|
+
},
|
|
1831
|
+
]);
|
|
1832
|
+
jest
|
|
1833
|
+
.spyOn(resultDataProvider, 'buildLinkedRequirementsByTestCase')
|
|
1834
|
+
.mockResolvedValueOnce(new Map([
|
|
1835
|
+
[
|
|
1836
|
+
42,
|
|
1837
|
+
{
|
|
1838
|
+
baseKeys: new Set([
|
|
1839
|
+
'SR0036',
|
|
1840
|
+
'SR0215',
|
|
1841
|
+
'SR0539',
|
|
1842
|
+
'SR0348',
|
|
1843
|
+
'SR0041',
|
|
1844
|
+
'SR0550',
|
|
1845
|
+
'SR0817',
|
|
1846
|
+
'SR0818',
|
|
1847
|
+
'SR0859',
|
|
1848
|
+
]),
|
|
1849
|
+
fullCodes: new Set([
|
|
1850
|
+
'SR0036',
|
|
1851
|
+
'SR0215',
|
|
1852
|
+
'SR0539',
|
|
1853
|
+
'SR0348',
|
|
1854
|
+
'SR0041',
|
|
1855
|
+
'SR0550',
|
|
1856
|
+
'SR0550-2',
|
|
1857
|
+
'SR0817',
|
|
1858
|
+
'SR0818',
|
|
1859
|
+
'SR0859',
|
|
1860
|
+
]),
|
|
1861
|
+
},
|
|
1862
|
+
],
|
|
1863
|
+
]));
|
|
1864
|
+
jest.spyOn(resultDataProvider.testStepParserHelper, 'parseTestSteps').mockResolvedValueOnce([
|
|
1865
|
+
{
|
|
1866
|
+
stepId: '1',
|
|
1867
|
+
stepPosition: '1',
|
|
1868
|
+
action: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
|
|
1869
|
+
expected: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. (SR0036; SR0215; VVRM-1; SR0817-V3.2; SR0818-V3.3; SR0859-V3.4)',
|
|
1870
|
+
isSharedStepTitle: false,
|
|
1871
|
+
},
|
|
1872
|
+
{
|
|
1873
|
+
stepId: '2',
|
|
1874
|
+
stepPosition: '2',
|
|
1875
|
+
action: 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
|
|
1876
|
+
expected: 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. (SR0539; SR0348; VVRM-1)',
|
|
1877
|
+
isSharedStepTitle: false,
|
|
1878
|
+
},
|
|
1879
|
+
{
|
|
1880
|
+
stepId: '3',
|
|
1881
|
+
stepPosition: '3',
|
|
1882
|
+
action: 'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.',
|
|
1883
|
+
expected: 'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. (SR0027, SR0036; SR0041; VVRM-2)',
|
|
1884
|
+
isSharedStepTitle: false,
|
|
1885
|
+
},
|
|
1886
|
+
{
|
|
1887
|
+
stepId: '4',
|
|
1888
|
+
stepPosition: '4',
|
|
1889
|
+
action: 'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
|
|
1890
|
+
expected: 'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. (SR0550-2)',
|
|
1891
|
+
isSharedStepTitle: false,
|
|
1892
|
+
},
|
|
1893
|
+
{
|
|
1894
|
+
stepId: '5',
|
|
1895
|
+
stepPosition: '5',
|
|
1896
|
+
action: 'Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit.',
|
|
1897
|
+
expected: 'Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit. (VVRM-1)',
|
|
1898
|
+
isSharedStepTitle: false,
|
|
1899
|
+
},
|
|
1900
|
+
{
|
|
1901
|
+
stepId: '6',
|
|
1902
|
+
stepPosition: '6',
|
|
1903
|
+
action: 'Neque porro quisquam est qui dolorem ipsum quia dolor sit amet consectetur adipisci velit.',
|
|
1904
|
+
expected: 'Neque porro quisquam est qui dolorem ipsum quia dolor sit amet consectetur adipisci velit. (SR0041; SR0550; VVRM-3)',
|
|
1905
|
+
isSharedStepTitle: false,
|
|
1906
|
+
},
|
|
1907
|
+
]);
|
|
1908
|
+
const result = await resultDataProvider.getMewpInternalValidationFlatResults('123', mockProjectName, [1]);
|
|
1909
|
+
expect(result.rows).toHaveLength(1);
|
|
1910
|
+
expect(result.rows[0]).toEqual(expect.objectContaining({
|
|
1911
|
+
'Test Case ID': 42,
|
|
1912
|
+
'Test Case Title': 'TC-0042',
|
|
1913
|
+
'Mentioned but Not Linked': 'Step 3: SR0027',
|
|
1914
|
+
'Linked but Not Mentioned': 'SR0817; SR0818; SR0859',
|
|
1915
|
+
'Validation Status': 'Fail',
|
|
1916
|
+
}));
|
|
1917
|
+
expect(String(result.rows[0]['Mentioned but Not Linked'] || '')).not.toContain('VVRM');
|
|
1918
|
+
expect(String(result.rows[0]['Linked but Not Mentioned'] || '')).not.toContain('VVRM');
|
|
1919
|
+
});
|
|
1920
|
+
});
|
|
1921
|
+
describe('MEWP rel fallback scoping', () => {
|
|
1922
|
+
it('should fallback to previous Rel run when latest selected Rel has no run for a test case', async () => {
|
|
1923
|
+
const suites = [
|
|
1924
|
+
{ testSuiteId: 10, suiteName: 'Rel10 / Validation' },
|
|
1925
|
+
{ testSuiteId: 11, suiteName: 'Rel11 / Validation' },
|
|
1926
|
+
];
|
|
1927
|
+
const allSuites = [
|
|
1928
|
+
{ testSuiteId: 10, suiteName: 'Rel10 / Validation' },
|
|
1929
|
+
{ testSuiteId: 11, suiteName: 'Rel11 / Validation' },
|
|
1930
|
+
];
|
|
1931
|
+
const rawTestData = [
|
|
1932
|
+
{
|
|
1933
|
+
suiteName: 'Rel10 / Validation',
|
|
1934
|
+
testPointsItems: [
|
|
1935
|
+
{ testCaseId: 501, lastRunId: 100, lastResultId: 200 },
|
|
1936
|
+
{ testCaseId: 502, lastRunId: 300, lastResultId: 400 },
|
|
1937
|
+
],
|
|
1938
|
+
testCasesItems: [{ workItem: { id: 501 } }, { workItem: { id: 502 } }],
|
|
1939
|
+
},
|
|
1940
|
+
{
|
|
1941
|
+
suiteName: 'Rel11 / Validation',
|
|
1942
|
+
testPointsItems: [
|
|
1943
|
+
{ testCaseId: 501, lastRunId: 0, lastResultId: 0 },
|
|
1944
|
+
{ testCaseId: 502, lastRunId: 500, lastResultId: 600 },
|
|
1945
|
+
],
|
|
1946
|
+
testCasesItems: [{ workItem: { id: 501 } }, { workItem: { id: 502 } }],
|
|
1947
|
+
},
|
|
1948
|
+
];
|
|
1949
|
+
const fetchSuitesSpy = jest
|
|
1950
|
+
.spyOn(resultDataProvider, 'fetchTestSuites')
|
|
1951
|
+
.mockResolvedValueOnce(suites)
|
|
1952
|
+
.mockResolvedValueOnce(allSuites);
|
|
1953
|
+
const fetchDataSpy = jest
|
|
1954
|
+
.spyOn(resultDataProvider, 'fetchTestData')
|
|
1955
|
+
.mockResolvedValueOnce(rawTestData);
|
|
1956
|
+
const scoped = await resultDataProvider.fetchMewpScopedTestData('123', mockProjectName, [11], true);
|
|
1957
|
+
expect(fetchSuitesSpy).toHaveBeenCalledTimes(2);
|
|
1958
|
+
expect(fetchDataSpy).toHaveBeenCalledTimes(1);
|
|
1959
|
+
expect(scoped).toHaveLength(1);
|
|
1960
|
+
const selectedPoints = scoped[0].testPointsItems;
|
|
1961
|
+
const tc501 = selectedPoints.find((item) => item.testCaseId === 501);
|
|
1962
|
+
const tc502 = selectedPoints.find((item) => item.testCaseId === 502);
|
|
1963
|
+
expect(tc501).toEqual(expect.objectContaining({ lastRunId: 100, lastResultId: 200 }));
|
|
1964
|
+
expect(tc502).toEqual(expect.objectContaining({ lastRunId: 500, lastResultId: 600 }));
|
|
1965
|
+
});
|
|
1966
|
+
});
|
|
1967
|
+
describe('MEWP external ingestion validation/parsing', () => {
|
|
1968
|
+
const validBugsSource = {
|
|
1969
|
+
name: 'bugs.xlsx',
|
|
1970
|
+
url: 'https://minio.local/mewp-external-ingestion/MEWP/mewp-external-ingestion/bugs/bugs.xlsx',
|
|
1971
|
+
sourceType: 'mewpExternalIngestion',
|
|
1972
|
+
};
|
|
1973
|
+
const validL3L4Source = {
|
|
1974
|
+
name: 'l3l4.xlsx',
|
|
1975
|
+
url: 'https://minio.local/mewp-external-ingestion/MEWP/mewp-external-ingestion/l3l4/l3l4.xlsx',
|
|
1976
|
+
sourceType: 'mewpExternalIngestion',
|
|
1977
|
+
};
|
|
1978
|
+
it('should validate external files when required columns exist in A3 header row', async () => {
|
|
1979
|
+
const bugsBuffer = buildWorkbookBuffer([
|
|
1980
|
+
['', '', '', '', ''],
|
|
1981
|
+
['', '', '', '', ''],
|
|
1982
|
+
['Elisra_SortIndex', 'SR', 'TargetWorkItemId', 'Title', 'TargetState'],
|
|
1983
|
+
['101', 'SR0001', '9001', 'Bug one', 'Active'],
|
|
1984
|
+
]);
|
|
1985
|
+
const l3l4Buffer = buildWorkbookBuffer([
|
|
1986
|
+
['', '', '', '', '', '', '', ''],
|
|
1987
|
+
['', '', '', '', '', '', '', ''],
|
|
1988
|
+
[
|
|
1989
|
+
'SR',
|
|
1990
|
+
'AREA 34',
|
|
1991
|
+
'TargetWorkItemId Level 3',
|
|
1992
|
+
'TargetTitleLevel3',
|
|
1993
|
+
'TargetStateLevel 3',
|
|
1994
|
+
'TargetWorkItemIdLevel 4',
|
|
1995
|
+
'TargetTitleLevel4',
|
|
1996
|
+
'TargetStateLevel 4',
|
|
1997
|
+
],
|
|
1998
|
+
['SR0001', 'Level 3', '7001', 'Req L3', 'Active', '', '', ''],
|
|
1999
|
+
]);
|
|
2000
|
+
const axiosSpy = jest
|
|
2001
|
+
.spyOn(axios_1.default, 'get')
|
|
2002
|
+
.mockResolvedValueOnce({ data: bugsBuffer, headers: {} })
|
|
2003
|
+
.mockResolvedValueOnce({ data: l3l4Buffer, headers: {} });
|
|
2004
|
+
const result = await resultDataProvider.validateMewpExternalFiles({
|
|
2005
|
+
externalBugsFile: validBugsSource,
|
|
2006
|
+
externalL3L4File: validL3L4Source,
|
|
2007
|
+
});
|
|
2008
|
+
expect(axiosSpy).toHaveBeenCalledTimes(2);
|
|
2009
|
+
expect(result.valid).toBe(true);
|
|
2010
|
+
expect(result.bugs).toEqual(expect.objectContaining({
|
|
2011
|
+
valid: true,
|
|
2012
|
+
headerRow: 'A3',
|
|
2013
|
+
matchedRequiredColumns: 5,
|
|
2014
|
+
}));
|
|
2015
|
+
expect(result.l3l4).toEqual(expect.objectContaining({
|
|
2016
|
+
valid: true,
|
|
2017
|
+
headerRow: 'A3',
|
|
2018
|
+
matchedRequiredColumns: 8,
|
|
2019
|
+
}));
|
|
2020
|
+
});
|
|
2021
|
+
it('should accept A1 fallback header row for backward compatibility', async () => {
|
|
2022
|
+
const bugsBuffer = buildWorkbookBuffer([
|
|
2023
|
+
['Elisra_SortIndex', 'SR', 'TargetWorkItemId', 'Title', 'TargetState'],
|
|
2024
|
+
['101', 'SR0001', '9001', 'Bug one', 'Active'],
|
|
2025
|
+
]);
|
|
2026
|
+
jest.spyOn(axios_1.default, 'get').mockResolvedValueOnce({ data: bugsBuffer, headers: {} });
|
|
2027
|
+
const result = await resultDataProvider.validateMewpExternalFiles({
|
|
2028
|
+
externalBugsFile: validBugsSource,
|
|
2029
|
+
});
|
|
2030
|
+
expect(result.valid).toBe(true);
|
|
2031
|
+
expect(result.bugs).toEqual(expect.objectContaining({
|
|
2032
|
+
valid: true,
|
|
2033
|
+
headerRow: 'A1',
|
|
2034
|
+
}));
|
|
2035
|
+
});
|
|
2036
|
+
it('should fail validation when required columns are missing', async () => {
|
|
2037
|
+
const invalidBuffer = buildWorkbookBuffer([
|
|
2038
|
+
['', '', ''],
|
|
2039
|
+
['', '', ''],
|
|
2040
|
+
['Elisra_SortIndex', 'TargetWorkItemId', 'TargetState'],
|
|
2041
|
+
['101', '9001', 'Active'],
|
|
2042
|
+
]);
|
|
2043
|
+
jest.spyOn(axios_1.default, 'get').mockResolvedValueOnce({ data: invalidBuffer, headers: {} });
|
|
2044
|
+
const result = await resultDataProvider.validateMewpExternalFiles({
|
|
2045
|
+
externalBugsFile: validBugsSource,
|
|
2046
|
+
});
|
|
2047
|
+
expect(result.valid).toBe(false);
|
|
2048
|
+
expect(result.bugs).toEqual(expect.objectContaining({
|
|
2049
|
+
valid: false,
|
|
2050
|
+
matchedRequiredColumns: 3,
|
|
2051
|
+
missingRequiredColumns: expect.arrayContaining(['SR', 'Title']),
|
|
2052
|
+
}));
|
|
2053
|
+
});
|
|
2054
|
+
it('should reject files from non-dedicated bucket/object path', async () => {
|
|
2055
|
+
var _a;
|
|
2056
|
+
const result = await resultDataProvider.validateMewpExternalFiles({
|
|
2057
|
+
externalBugsFile: {
|
|
2058
|
+
name: 'bugs.xlsx',
|
|
2059
|
+
url: 'https://minio.local/mewp-external-ingestion/MEWP/other-prefix/bugs.xlsx',
|
|
2060
|
+
sourceType: 'mewpExternalIngestion',
|
|
2061
|
+
},
|
|
2062
|
+
});
|
|
2063
|
+
expect(result.valid).toBe(false);
|
|
2064
|
+
expect(((_a = result.bugs) === null || _a === void 0 ? void 0 : _a.message) || '').toContain('Invalid object path');
|
|
2065
|
+
});
|
|
2066
|
+
it('should filter external bugs by SR/state and dedupe by bug+requirement', async () => {
|
|
2067
|
+
const mewpExternalTableUtils = resultDataProvider.mewpExternalTableUtils;
|
|
2068
|
+
jest.spyOn(mewpExternalTableUtils, 'loadExternalTableRows').mockResolvedValueOnce([
|
|
2069
|
+
{
|
|
2070
|
+
Elisra_SortIndex: '101',
|
|
2071
|
+
SR: 'SR0001',
|
|
2072
|
+
TargetWorkItemId: '9001',
|
|
2073
|
+
Title: 'Bug one',
|
|
2074
|
+
TargetState: 'Active',
|
|
2075
|
+
SAPWBS: 'ESUK',
|
|
2076
|
+
},
|
|
2077
|
+
{
|
|
2078
|
+
Elisra_SortIndex: '101',
|
|
2079
|
+
SR: 'SR0001',
|
|
2080
|
+
TargetWorkItemId: '9001',
|
|
2081
|
+
Title: 'Bug one duplicate',
|
|
2082
|
+
TargetState: 'Active',
|
|
2083
|
+
},
|
|
2084
|
+
{
|
|
2085
|
+
Elisra_SortIndex: '101',
|
|
2086
|
+
SR: '',
|
|
2087
|
+
TargetWorkItemId: '9002',
|
|
2088
|
+
Title: 'Missing SR',
|
|
2089
|
+
TargetState: 'Active',
|
|
2090
|
+
},
|
|
2091
|
+
{
|
|
2092
|
+
Elisra_SortIndex: '101',
|
|
2093
|
+
SR: 'SR0002',
|
|
2094
|
+
TargetWorkItemId: '9003',
|
|
2095
|
+
Title: 'Closed bug',
|
|
2096
|
+
TargetState: 'Closed',
|
|
2097
|
+
},
|
|
2098
|
+
]);
|
|
2099
|
+
const map = await resultDataProvider.loadExternalBugsByTestCase(validBugsSource);
|
|
2100
|
+
const bugs = map.get(101) || [];
|
|
2101
|
+
expect(bugs).toHaveLength(1);
|
|
2102
|
+
expect(bugs[0]).toEqual(expect.objectContaining({
|
|
2103
|
+
id: 9001,
|
|
2104
|
+
requirementBaseKey: 'SR0001',
|
|
2105
|
+
responsibility: 'ESUK',
|
|
2106
|
+
}));
|
|
2107
|
+
});
|
|
2108
|
+
it('should require Elisra_SortIndex and ignore rows that only provide WorkItemId', async () => {
|
|
2109
|
+
const mewpExternalTableUtils = resultDataProvider.mewpExternalTableUtils;
|
|
2110
|
+
jest.spyOn(mewpExternalTableUtils, 'loadExternalTableRows').mockResolvedValueOnce([
|
|
2111
|
+
{
|
|
2112
|
+
WorkItemId: '101',
|
|
2113
|
+
SR: 'SR0001',
|
|
2114
|
+
TargetWorkItemId: '9010',
|
|
2115
|
+
Title: 'Bug without Elisra_SortIndex',
|
|
2116
|
+
TargetState: 'Active',
|
|
2117
|
+
},
|
|
2118
|
+
]);
|
|
2119
|
+
const map = await resultDataProvider.loadExternalBugsByTestCase(validBugsSource);
|
|
2120
|
+
expect(map.size).toBe(0);
|
|
2121
|
+
});
|
|
2122
|
+
it('should parse external L3/L4 file using AREA 34 semantics and terminal-state filtering', async () => {
|
|
2123
|
+
const mewpExternalTableUtils = resultDataProvider.mewpExternalTableUtils;
|
|
2124
|
+
jest.spyOn(mewpExternalTableUtils, 'loadExternalTableRows').mockResolvedValueOnce([
|
|
2125
|
+
{
|
|
2126
|
+
SR: 'SR0001',
|
|
2127
|
+
'AREA 34': 'Level 4',
|
|
2128
|
+
'TargetWorkItemId Level 3': '7001',
|
|
2129
|
+
TargetTitleLevel3: 'L4 From Level3 Column',
|
|
2130
|
+
'TargetStateLevel 3': 'Active',
|
|
2131
|
+
},
|
|
2132
|
+
{
|
|
2133
|
+
SR: 'SR0001',
|
|
2134
|
+
'AREA 34': 'Level 3',
|
|
2135
|
+
'TargetWorkItemId Level 3': '7002',
|
|
2136
|
+
TargetTitleLevel3: 'L3 Requirement',
|
|
2137
|
+
'TargetStateLevel 3': 'Active',
|
|
2138
|
+
'TargetWorkItemIdLevel 4': '7003',
|
|
2139
|
+
TargetTitleLevel4: 'L4 Requirement',
|
|
2140
|
+
'TargetStateLevel 4': 'Closed',
|
|
2141
|
+
},
|
|
2142
|
+
]);
|
|
2143
|
+
const map = await resultDataProvider.loadExternalL3L4ByBaseKey(validL3L4Source);
|
|
2144
|
+
expect(map.get('SR0001')).toEqual([
|
|
2145
|
+
{ id: '7002', title: 'L3 Requirement', level: 'L3' },
|
|
2146
|
+
{ id: '7001', title: 'L4 From Level3 Column', level: 'L4' },
|
|
2147
|
+
]);
|
|
2148
|
+
});
|
|
2149
|
+
it('should exclude external open L3/L4 rows when SAPWBS resolves to ESUK', async () => {
|
|
2150
|
+
const mewpExternalTableUtils = resultDataProvider.mewpExternalTableUtils;
|
|
2151
|
+
jest.spyOn(mewpExternalTableUtils, 'loadExternalTableRows').mockResolvedValueOnce([
|
|
2152
|
+
{
|
|
2153
|
+
SR: 'SR0001',
|
|
2154
|
+
'AREA 34': 'Level 3',
|
|
2155
|
+
'TargetWorkItemId Level 3': '7101',
|
|
2156
|
+
TargetTitleLevel3: 'L3 ESUK',
|
|
2157
|
+
'TargetStateLevel 3': 'Active',
|
|
2158
|
+
'TargetSapWbsLevel 3': 'ESUK',
|
|
2159
|
+
'TargetWorkItemIdLevel 4': '7201',
|
|
2160
|
+
TargetTitleLevel4: 'L4 IL',
|
|
2161
|
+
'TargetStateLevel 4': 'Active',
|
|
2162
|
+
'TargetSapWbsLevel 4': 'IL',
|
|
2163
|
+
},
|
|
2164
|
+
{
|
|
2165
|
+
SR: 'SR0001',
|
|
2166
|
+
'AREA 34': 'Level 3',
|
|
2167
|
+
'TargetWorkItemId Level 3': '7102',
|
|
2168
|
+
TargetTitleLevel3: 'L3 IL',
|
|
2169
|
+
'TargetStateLevel 3': 'Active',
|
|
2170
|
+
'TargetSapWbsLevel 3': 'IL',
|
|
2171
|
+
},
|
|
2172
|
+
]);
|
|
2173
|
+
const map = await resultDataProvider.loadExternalL3L4ByBaseKey(validL3L4Source);
|
|
2174
|
+
expect(map.get('SR0001')).toEqual([
|
|
2175
|
+
{ id: '7102', title: 'L3 IL', level: 'L3' },
|
|
2176
|
+
{ id: '7201', title: 'L4 IL', level: 'L4' },
|
|
2177
|
+
]);
|
|
2178
|
+
});
|
|
2179
|
+
it('should fallback L3/L4 SAPWBS exclusion from SR-mapped requirement when row SAPWBS is empty', async () => {
|
|
2180
|
+
const mewpExternalTableUtils = resultDataProvider.mewpExternalTableUtils;
|
|
2181
|
+
jest.spyOn(mewpExternalTableUtils, 'loadExternalTableRows').mockResolvedValueOnce([
|
|
2182
|
+
{
|
|
2183
|
+
SR: 'SR0001',
|
|
2184
|
+
'AREA 34': 'Level 3',
|
|
2185
|
+
'TargetWorkItemId Level 3': '7301',
|
|
2186
|
+
TargetTitleLevel3: 'L3 From ESUK Requirement',
|
|
2187
|
+
'TargetStateLevel 3': 'Active',
|
|
2188
|
+
'TargetSapWbsLevel 3': '',
|
|
2189
|
+
},
|
|
2190
|
+
{
|
|
2191
|
+
SR: 'SR0002',
|
|
2192
|
+
'AREA 34': 'Level 3',
|
|
2193
|
+
'TargetWorkItemId Level 3': '7302',
|
|
2194
|
+
TargetTitleLevel3: 'L3 From IL Requirement',
|
|
2195
|
+
'TargetStateLevel 3': 'Active',
|
|
2196
|
+
'TargetSapWbsLevel 3': '',
|
|
2197
|
+
},
|
|
2198
|
+
]);
|
|
2199
|
+
const map = await resultDataProvider.loadExternalL3L4ByBaseKey(validL3L4Source, new Map([
|
|
2200
|
+
['SR0001', 'ESUK'],
|
|
2201
|
+
['SR0002', 'IL'],
|
|
2202
|
+
]));
|
|
2203
|
+
expect(map.has('SR0001')).toBe(false);
|
|
2204
|
+
expect(map.get('SR0002')).toEqual([{ id: '7302', title: 'L3 From IL Requirement', level: 'L3' }]);
|
|
2205
|
+
});
|
|
2206
|
+
it('should resolve bug responsibility from AreaPath when SAPWBS is empty', () => {
|
|
2207
|
+
const fromEsukAreaPath = resultDataProvider.resolveBugResponsibility({
|
|
2208
|
+
'Custom.SAPWBS': '',
|
|
2209
|
+
'System.AreaPath': 'MEWP\\Customer Requirements\\Level 2\\ATP\\ESUK',
|
|
2210
|
+
});
|
|
2211
|
+
const fromIlAreaPath = resultDataProvider.resolveBugResponsibility({
|
|
2212
|
+
'Custom.SAPWBS': '',
|
|
2213
|
+
'System.AreaPath': 'MEWP\\Customer Requirements\\Level 2\\ATP',
|
|
2214
|
+
});
|
|
2215
|
+
const unknown = resultDataProvider.resolveBugResponsibility({
|
|
2216
|
+
'Custom.SAPWBS': '',
|
|
2217
|
+
'System.AreaPath': 'MEWP\\Other\\Area',
|
|
2218
|
+
});
|
|
2219
|
+
expect(fromEsukAreaPath).toBe('ESUK');
|
|
2220
|
+
expect(fromIlAreaPath).toBe('Elisra');
|
|
2221
|
+
expect(unknown).toBe('Unknown');
|
|
2222
|
+
});
|
|
2223
|
+
it('should handle 1000 external bug rows and keep only in-scope parsed items', async () => {
|
|
2224
|
+
const rows = [];
|
|
2225
|
+
for (let i = 1; i <= 1000; i++) {
|
|
2226
|
+
rows.push({
|
|
2227
|
+
Elisra_SortIndex: String(4000 + (i % 20)),
|
|
2228
|
+
SR: `SR${String(6000 + (i % 50)).padStart(4, '0')}`,
|
|
2229
|
+
TargetWorkItemId: String(900000 + i),
|
|
2230
|
+
Title: `Bug ${i}`,
|
|
2231
|
+
TargetState: i % 10 === 0 ? 'Closed' : 'Active',
|
|
2232
|
+
SAPWBS: i % 2 === 0 ? 'ESUK' : 'IL',
|
|
2233
|
+
});
|
|
2234
|
+
}
|
|
2235
|
+
const mewpExternalTableUtils = resultDataProvider.mewpExternalTableUtils;
|
|
2236
|
+
jest.spyOn(mewpExternalTableUtils, 'loadExternalTableRows').mockResolvedValueOnce(rows);
|
|
2237
|
+
const startedAt = Date.now();
|
|
2238
|
+
const map = await resultDataProvider.loadExternalBugsByTestCase({
|
|
2239
|
+
name: 'bulk-bugs.xlsx',
|
|
2240
|
+
url: 'https://minio.local/mewp-external-ingestion/MEWP/mewp-external-ingestion/bugs/bulk-bugs.xlsx',
|
|
2241
|
+
sourceType: 'mewpExternalIngestion',
|
|
2242
|
+
});
|
|
2243
|
+
const elapsedMs = Date.now() - startedAt;
|
|
2244
|
+
const totalParsed = [...map.values()].reduce((sum, items) => sum + ((items === null || items === void 0 ? void 0 : items.length) || 0), 0);
|
|
2245
|
+
expect(totalParsed).toBe(900); // every 10th row is closed and filtered out
|
|
2246
|
+
expect(elapsedMs).toBeLessThan(5000);
|
|
2247
|
+
});
|
|
2248
|
+
it('should handle 1000 external L3/L4 rows and map all active links', async () => {
|
|
2249
|
+
const rows = [];
|
|
2250
|
+
for (let i = 1; i <= 1000; i++) {
|
|
2251
|
+
rows.push({
|
|
2252
|
+
SR: `SR${String(7000 + (i % 25)).padStart(4, '0')}`,
|
|
2253
|
+
'AREA 34': i % 3 === 0 ? 'Level 4' : 'Level 3',
|
|
2254
|
+
'TargetWorkItemId Level 3': String(800000 + i),
|
|
2255
|
+
TargetTitleLevel3: `L3/L4 Title ${i}`,
|
|
2256
|
+
'TargetStateLevel 3': i % 11 === 0 ? 'Resolved' : 'Active',
|
|
2257
|
+
'TargetWorkItemIdLevel 4': String(810000 + i),
|
|
2258
|
+
TargetTitleLevel4: `L4 Title ${i}`,
|
|
2259
|
+
'TargetStateLevel 4': i % 13 === 0 ? 'Closed' : 'Active',
|
|
2260
|
+
});
|
|
2261
|
+
}
|
|
2262
|
+
const mewpExternalTableUtils = resultDataProvider.mewpExternalTableUtils;
|
|
2263
|
+
jest.spyOn(mewpExternalTableUtils, 'loadExternalTableRows').mockResolvedValueOnce(rows);
|
|
2264
|
+
const startedAt = Date.now();
|
|
2265
|
+
const map = await resultDataProvider.loadExternalL3L4ByBaseKey({
|
|
2266
|
+
name: 'bulk-l3l4.xlsx',
|
|
2267
|
+
url: 'https://minio.local/mewp-external-ingestion/MEWP/mewp-external-ingestion/l3l4/bulk-l3l4.xlsx',
|
|
2268
|
+
sourceType: 'mewpExternalIngestion',
|
|
2269
|
+
});
|
|
2270
|
+
const elapsedMs = Date.now() - startedAt;
|
|
2271
|
+
const totalLinks = [...map.values()].reduce((sum, items) => sum + ((items === null || items === void 0 ? void 0 : items.length) || 0), 0);
|
|
2272
|
+
expect(totalLinks).toBeGreaterThan(700);
|
|
2273
|
+
expect(elapsedMs).toBeLessThan(5000);
|
|
2274
|
+
});
|
|
2275
|
+
});
|
|
2276
|
+
describe('MEWP high-volume requirement token parsing', () => {
|
|
2277
|
+
it('should parse 1000 expected-result requirement tokens with noisy fragments', () => {
|
|
2278
|
+
const tokens = [];
|
|
2279
|
+
for (let i = 1; i <= 1000; i++) {
|
|
2280
|
+
const code = `SR${String(10000 + i)}`;
|
|
2281
|
+
tokens.push(code);
|
|
2282
|
+
if (i % 100 === 0)
|
|
2283
|
+
tokens.push(`${code} V3.24`);
|
|
2284
|
+
if (i % 125 === 0)
|
|
2285
|
+
tokens.push(`${code} VVRM2425`);
|
|
2286
|
+
}
|
|
2287
|
+
const sourceText = tokens.join('; ');
|
|
2288
|
+
const startedAt = Date.now();
|
|
2289
|
+
const codes = resultDataProvider.extractRequirementCodesFromText(sourceText);
|
|
2290
|
+
const elapsedMs = Date.now() - startedAt;
|
|
2291
|
+
expect(codes.size).toBe(1000);
|
|
2292
|
+
expect(codes.has('SR10001')).toBe(true);
|
|
2293
|
+
expect(codes.has('SR11000')).toBe(true);
|
|
2294
|
+
expect(elapsedMs).toBeLessThan(3000);
|
|
1115
2295
|
});
|
|
1116
2296
|
});
|
|
1117
2297
|
describe('fetchResultDataForTestReporter (runResultField switch)', () => {
|