github-issue-tower-defence-management 1.153.6 → 1.154.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.
Files changed (24) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +26 -6
  3. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
  4. package/bin/adapter/repositories/issue/RestIssueRepository.js +6 -0
  5. package/bin/adapter/repositories/issue/RestIssueRepository.js.map +1 -1
  6. package/bin/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.js +10 -16
  7. package/bin/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +3 -0
  10. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +58 -0
  11. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +36 -6
  12. package/src/adapter/repositories/issue/RestIssueRepository.test.ts +19 -0
  13. package/src/adapter/repositories/issue/RestIssueRepository.ts +13 -0
  14. package/src/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.test.ts +211 -200
  15. package/src/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.ts +13 -18
  16. package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +5 -0
  17. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +6 -2
  18. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
  19. package/types/adapter/repositories/issue/RestIssueRepository.d.ts +1 -0
  20. package/types/adapter/repositories/issue/RestIssueRepository.d.ts.map +1 -1
  21. package/types/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.d.ts +2 -2
  22. package/types/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.d.ts.map +1 -1
  23. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +2 -0
  24. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
@@ -48,6 +48,12 @@ describe('ConvertCheckboxToIssueInStoryIssueUseCase', () => {
48
48
  repo: 'repo',
49
49
  };
50
50
 
51
+ const storyIssue1WithStoryNamePlaceholder = {
52
+ ...basicStoryIssue1,
53
+ body: `- [ ] Task 1
54
+ - [ ] Task 2 for \`STORYNAME\``,
55
+ };
56
+
51
57
  const basicStoryObject1: StoryObject = {
52
58
  story: { ...mock<StoryOption>(), id: 'story1', name: 'Story 1' },
53
59
  storyIssue: basicStoryIssue1,
@@ -127,9 +133,9 @@ describe('ConvertCheckboxToIssueInStoryIssueUseCase', () => {
127
133
  string[],
128
134
  string[],
129
135
  ][];
130
- expectedUpdateIssueCalls: [Issue][];
136
+ expectedUpdateIssueBodyCalls: [Issue, string][];
131
137
  expectedUpdateStoryCalls: [Project, Issue, string][];
132
- expectedGetIssueByUrlCalls: [string][];
138
+ expectedIssueUrlReads: [string][];
133
139
  expectedAddIssueToProjectCalls: [Project, string][];
134
140
  }[] = [
135
141
  {
@@ -144,9 +150,9 @@ describe('ConvertCheckboxToIssueInStoryIssueUseCase', () => {
144
150
  createTaskFromStoryBodyCheckboxEnabled: true,
145
151
  },
146
152
  expectedCreateNewIssueCalls: [],
147
- expectedUpdateIssueCalls: [],
153
+ expectedUpdateIssueBodyCalls: [],
148
154
  expectedUpdateStoryCalls: [],
149
- expectedGetIssueByUrlCalls: [],
155
+ expectedIssueUrlReads: [],
150
156
  expectedAddIssueToProjectCalls: [],
151
157
  },
152
158
  {
@@ -194,60 +200,48 @@ describe('ConvertCheckboxToIssueInStoryIssueUseCase', () => {
194
200
  [],
195
201
  ],
196
202
  ],
197
- expectedUpdateIssueCalls: [
203
+ expectedUpdateIssueBodyCalls: [
198
204
  [
199
- {
200
- ...basicStoryIssue1,
201
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%201
205
+ basicStoryIssue1,
206
+ `https://example.com?sliceBy%5Bvalue%5D=Story%201
202
207
 
203
208
  - [ ] Task 1
204
209
  - [ ] Task 2`,
205
- },
206
210
  ],
207
211
  [
208
- {
209
- ...basicStoryIssue1,
210
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%201
212
+ basicStoryIssue1,
213
+ `https://example.com?sliceBy%5Bvalue%5D=Story%201
211
214
 
212
215
  - [ ] https://github.com/org/repo/issues/1
213
216
  - [ ] Task 2`,
214
- },
215
217
  ],
216
218
  [
217
- {
218
- ...basicStoryIssue1,
219
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%201
219
+ basicStoryIssue1,
220
+ `https://example.com?sliceBy%5Bvalue%5D=Story%201
220
221
 
221
222
  - [ ] https://github.com/org/repo/issues/1
222
223
  - [ ] https://github.com/org/repo/issues/2`,
223
- },
224
224
  ],
225
225
  [
226
- {
227
- ...basicStoryIssue2,
228
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%202
226
+ basicStoryIssue2,
227
+ `https://example.com?sliceBy%5Bvalue%5D=Story%202
229
228
 
230
229
  - [ ] Task 3
231
230
  - [ ] Task 4`,
232
- },
233
231
  ],
234
232
  [
235
- {
236
- ...basicStoryIssue2,
237
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%202
233
+ basicStoryIssue2,
234
+ `https://example.com?sliceBy%5Bvalue%5D=Story%202
238
235
 
239
236
  - [ ] https://github.com/org/repo/issues/3
240
237
  - [ ] Task 4`,
241
- },
242
238
  ],
243
239
  [
244
- {
245
- ...basicStoryIssue2,
246
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%202
240
+ basicStoryIssue2,
241
+ `https://example.com?sliceBy%5Bvalue%5D=Story%202
247
242
 
248
243
  - [ ] https://github.com/org/repo/issues/3
249
244
  - [ ] https://github.com/org/repo/issues/4`,
250
- },
251
245
  ],
252
246
  ],
253
247
  expectedUpdateStoryCalls: [
@@ -284,7 +278,7 @@ describe('ConvertCheckboxToIssueInStoryIssueUseCase', () => {
284
278
  'story2',
285
279
  ],
286
280
  ],
287
- expectedGetIssueByUrlCalls: [
281
+ expectedIssueUrlReads: [
288
282
  ['https://github.com/org/repo/issues/123'],
289
283
  ['https://github.com/org/repo/issues/1'],
290
284
  ['https://github.com/org/repo/issues/2'],
@@ -311,9 +305,9 @@ describe('ConvertCheckboxToIssueInStoryIssueUseCase', () => {
311
305
  createTaskFromStoryBodyCheckboxEnabled: true,
312
306
  },
313
307
  expectedCreateNewIssueCalls: [],
314
- expectedUpdateIssueCalls: [],
308
+ expectedUpdateIssueBodyCalls: [],
315
309
  expectedUpdateStoryCalls: [],
316
- expectedGetIssueByUrlCalls: [],
310
+ expectedIssueUrlReads: [],
317
311
  expectedAddIssueToProjectCalls: [],
318
312
  },
319
313
  {
@@ -329,9 +323,9 @@ describe('ConvertCheckboxToIssueInStoryIssueUseCase', () => {
329
323
  },
330
324
  expectedThrowError: new Error('Story issue not found: Story 1'),
331
325
  expectedCreateNewIssueCalls: [],
332
- expectedUpdateIssueCalls: [],
326
+ expectedUpdateIssueBodyCalls: [],
333
327
  expectedUpdateStoryCalls: [],
334
- expectedGetIssueByUrlCalls: [],
328
+ expectedIssueUrlReads: [],
335
329
  expectedAddIssueToProjectCalls: [],
336
330
  },
337
331
 
@@ -357,22 +351,20 @@ describe('ConvertCheckboxToIssueInStoryIssueUseCase', () => {
357
351
  createTaskFromStoryBodyCheckboxEnabled: true,
358
352
  },
359
353
  expectedCreateNewIssueCalls: [],
360
- expectedUpdateIssueCalls: [
354
+ expectedUpdateIssueBodyCalls: [
361
355
  [
362
356
  {
363
357
  ...basicStoryIssue2,
364
358
  status: 'Icebox',
365
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%202
359
+ },
360
+ `https://example.com?sliceBy%5Bvalue%5D=Story%202
366
361
 
367
362
  - [ ] Task 3
368
363
  - [ ] Task 4`,
369
- },
370
364
  ],
371
365
  ],
372
366
  expectedUpdateStoryCalls: [],
373
- expectedGetIssueByUrlCalls: [
374
- ['https://github.com/org/repo/issues/456'],
375
- ],
367
+ expectedIssueUrlReads: [['https://github.com/org/repo/issues/456']],
376
368
  expectedAddIssueToProjectCalls: [],
377
369
  },
378
370
  {
@@ -400,24 +392,22 @@ describe('ConvertCheckboxToIssueInStoryIssueUseCase', () => {
400
392
  createTaskFromStoryBodyCheckboxEnabled: false,
401
393
  },
402
394
  expectedCreateNewIssueCalls: [],
403
- expectedUpdateIssueCalls: [
395
+ expectedUpdateIssueBodyCalls: [
404
396
  [
405
397
  {
406
398
  ...basicStoryIssue1,
407
399
  title: 'Story 1',
408
400
  number: 901,
409
401
  url: 'https://github.com/org/repo/issues/901',
410
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%201
402
+ },
403
+ `https://example.com?sliceBy%5Bvalue%5D=Story%201
411
404
 
412
405
  - [ ] Task 1
413
406
  - [ ] Task 2`,
414
- },
415
407
  ],
416
408
  ],
417
409
  expectedUpdateStoryCalls: [],
418
- expectedGetIssueByUrlCalls: [
419
- ['https://github.com/org/repo/issues/901'],
420
- ],
410
+ expectedIssueUrlReads: [['https://github.com/org/repo/issues/901']],
421
411
  expectedAddIssueToProjectCalls: [],
422
412
  },
423
413
  {
@@ -445,24 +435,22 @@ describe('ConvertCheckboxToIssueInStoryIssueUseCase', () => {
445
435
  createTaskFromStoryBodyCheckboxEnabled: false,
446
436
  },
447
437
  expectedCreateNewIssueCalls: [],
448
- expectedUpdateIssueCalls: [
438
+ expectedUpdateIssueBodyCalls: [
449
439
  [
450
440
  {
451
441
  ...basicStoryIssue1,
452
442
  title: 'Story 1',
453
443
  number: 901,
454
444
  url: 'https://github.com/org/repo/issues/901',
455
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%201
445
+ },
446
+ `https://example.com?sliceBy%5Bvalue%5D=Story%201
456
447
 
457
448
  - [ ] Task 1
458
449
  - [ ] Task 2`,
459
- },
460
450
  ],
461
451
  ],
462
452
  expectedUpdateStoryCalls: [],
463
- expectedGetIssueByUrlCalls: [
464
- ['https://github.com/org/repo/issues/901'],
465
- ],
453
+ expectedIssueUrlReads: [['https://github.com/org/repo/issues/901']],
466
454
  expectedAddIssueToProjectCalls: [],
467
455
  },
468
456
  {
@@ -486,9 +474,9 @@ describe('ConvertCheckboxToIssueInStoryIssueUseCase', () => {
486
474
  createTaskFromStoryBodyCheckboxEnabled: true,
487
475
  },
488
476
  expectedCreateNewIssueCalls: [],
489
- expectedUpdateIssueCalls: [],
477
+ expectedUpdateIssueBodyCalls: [],
490
478
  expectedUpdateStoryCalls: [],
491
- expectedGetIssueByUrlCalls: [],
479
+ expectedIssueUrlReads: [],
492
480
  expectedAddIssueToProjectCalls: [],
493
481
  },
494
482
  {
@@ -536,60 +524,48 @@ describe('ConvertCheckboxToIssueInStoryIssueUseCase', () => {
536
524
  [],
537
525
  ],
538
526
  ],
539
- expectedUpdateIssueCalls: [
527
+ expectedUpdateIssueBodyCalls: [
540
528
  [
541
- {
542
- ...basicStoryIssue1,
543
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%201
529
+ basicStoryIssue1,
530
+ `https://example.com?sliceBy%5Bvalue%5D=Story%201
544
531
 
545
532
  - [ ] Task 1
546
533
  - [ ] Task 2`,
547
- },
548
534
  ],
549
535
  [
550
- {
551
- ...basicStoryIssue1,
552
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%201
536
+ basicStoryIssue1,
537
+ `https://example.com?sliceBy%5Bvalue%5D=Story%201
553
538
 
554
539
  - [ ] https://github.com/org/repo/issues/1
555
540
  - [ ] Task 2`,
556
- },
557
541
  ],
558
542
  [
559
- {
560
- ...basicStoryIssue1,
561
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%201
543
+ basicStoryIssue1,
544
+ `https://example.com?sliceBy%5Bvalue%5D=Story%201
562
545
 
563
546
  - [ ] https://github.com/org/repo/issues/1
564
547
  - [ ] https://github.com/org/repo/issues/2`,
565
- },
566
548
  ],
567
549
  [
568
- {
569
- ...basicStoryIssue2,
570
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%202
550
+ basicStoryIssue2,
551
+ `https://example.com?sliceBy%5Bvalue%5D=Story%202
571
552
 
572
553
  - [ ] Task 3
573
554
  - [ ] Task 4`,
574
- },
575
555
  ],
576
556
  [
577
- {
578
- ...basicStoryIssue2,
579
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%202
557
+ basicStoryIssue2,
558
+ `https://example.com?sliceBy%5Bvalue%5D=Story%202
580
559
 
581
560
  - [ ] https://github.com/org/repo/issues/3
582
561
  - [ ] Task 4`,
583
- },
584
562
  ],
585
563
  [
586
- {
587
- ...basicStoryIssue2,
588
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%202
564
+ basicStoryIssue2,
565
+ `https://example.com?sliceBy%5Bvalue%5D=Story%202
589
566
 
590
567
  - [ ] https://github.com/org/repo/issues/3
591
568
  - [ ] https://github.com/org/repo/issues/4`,
592
- },
593
569
  ],
594
570
  ],
595
571
  expectedUpdateStoryCalls: [
@@ -626,7 +602,7 @@ describe('ConvertCheckboxToIssueInStoryIssueUseCase', () => {
626
602
  'story2',
627
603
  ],
628
604
  ],
629
- expectedGetIssueByUrlCalls: [
605
+ expectedIssueUrlReads: [
630
606
  ['https://github.com/org/repo/issues/123'],
631
607
  ['https://github.com/org/repo/issues/1'],
632
608
  ['https://github.com/org/repo/issues/2'],
@@ -677,14 +653,17 @@ describe('ConvertCheckboxToIssueInStoryIssueUseCase', () => {
677
653
  [],
678
654
  ],
679
655
  ],
680
- expectedUpdateIssueCalls: [
656
+ expectedUpdateIssueBodyCalls: [
681
657
  [
682
658
  {
683
659
  ...basicStoryIssue1,
684
660
  body: `https://example.com?sliceBy%5Bvalue%5D=Story%201
685
661
 
686
- - [ ] https://github.com/org/repo/issues/1`,
662
+ - [ ] Task 1`,
687
663
  },
664
+ `https://example.com?sliceBy%5Bvalue%5D=Story%201
665
+
666
+ - [ ] https://github.com/org/repo/issues/1`,
688
667
  ],
689
668
  ],
690
669
  expectedUpdateStoryCalls: [
@@ -705,7 +684,7 @@ describe('ConvertCheckboxToIssueInStoryIssueUseCase', () => {
705
684
  'story1',
706
685
  ],
707
686
  ],
708
- expectedGetIssueByUrlCalls: [
687
+ expectedIssueUrlReads: [
709
688
  ['https://github.com/org/repo/issues/123'],
710
689
  ['https://github.com/org/repo/issues/1'],
711
690
  ],
@@ -760,34 +739,26 @@ describe('ConvertCheckboxToIssueInStoryIssueUseCase', () => {
760
739
  createTaskFromStoryBodyCheckboxEnabled: true,
761
740
  },
762
741
  expectedCreateNewIssueCalls: [],
763
- expectedUpdateIssueCalls: [
742
+ expectedUpdateIssueBodyCalls: [
764
743
  [
765
744
  {
766
745
  ...basicStoryIssue1,
767
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%201
746
+ body: 'Some description without checkboxes',
747
+ },
748
+ `https://example.com?sliceBy%5Bvalue%5D=Story%201
768
749
 
769
750
  Some description without checkboxes`,
770
- },
771
751
  ],
772
752
  ],
773
753
  expectedUpdateStoryCalls: [],
774
- expectedGetIssueByUrlCalls: [
775
- ['https://github.com/org/repo/issues/123'],
776
- ],
754
+ expectedIssueUrlReads: [['https://github.com/org/repo/issues/123']],
777
755
  expectedAddIssueToProjectCalls: [],
778
756
  },
779
757
  {
780
758
  name: 'should create new issues with replaced STORYNAME for checkboxes and update story issue',
781
759
  input: {
782
760
  project: basicProject,
783
- issues: [
784
- {
785
- ...basicStoryIssue1,
786
- body: `- [ ] Task 1
787
- - [ ] Task 2 for \`STORYNAME\``,
788
- },
789
- basicStoryIssue2,
790
- ],
761
+ issues: [storyIssue1WithStoryNamePlaceholder, basicStoryIssue2],
791
762
  cacheUsed: false,
792
763
  urlOfStoryView: 'https://example.com',
793
764
  storyObjectMap: basicStoryObjectMap,
@@ -828,60 +799,48 @@ Some description without checkboxes`,
828
799
  [],
829
800
  ],
830
801
  ],
831
- expectedUpdateIssueCalls: [
802
+ expectedUpdateIssueBodyCalls: [
832
803
  [
833
- {
834
- ...basicStoryIssue1,
835
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%201
804
+ storyIssue1WithStoryNamePlaceholder,
805
+ `https://example.com?sliceBy%5Bvalue%5D=Story%201
836
806
 
837
807
  - [ ] Task 1
838
808
  - [ ] Task 2 for \`STORYNAME\``,
839
- },
840
809
  ],
841
810
  [
842
- {
843
- ...basicStoryIssue1,
844
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%201
811
+ storyIssue1WithStoryNamePlaceholder,
812
+ `https://example.com?sliceBy%5Bvalue%5D=Story%201
845
813
 
846
814
  - [ ] https://github.com/org/repo/issues/1
847
815
  - [ ] Task 2 for \`STORYNAME\``,
848
- },
849
816
  ],
850
817
  [
851
- {
852
- ...basicStoryIssue1,
853
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%201
818
+ storyIssue1WithStoryNamePlaceholder,
819
+ `https://example.com?sliceBy%5Bvalue%5D=Story%201
854
820
 
855
821
  - [ ] https://github.com/org/repo/issues/1
856
822
  - [ ] https://github.com/org/repo/issues/2`,
857
- },
858
823
  ],
859
824
  [
860
- {
861
- ...basicStoryIssue2,
862
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%202
825
+ basicStoryIssue2,
826
+ `https://example.com?sliceBy%5Bvalue%5D=Story%202
863
827
 
864
828
  - [ ] Task 3
865
829
  - [ ] Task 4`,
866
- },
867
830
  ],
868
831
  [
869
- {
870
- ...basicStoryIssue2,
871
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%202
832
+ basicStoryIssue2,
833
+ `https://example.com?sliceBy%5Bvalue%5D=Story%202
872
834
 
873
835
  - [ ] https://github.com/org/repo/issues/3
874
836
  - [ ] Task 4`,
875
- },
876
837
  ],
877
838
  [
878
- {
879
- ...basicStoryIssue2,
880
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%202
839
+ basicStoryIssue2,
840
+ `https://example.com?sliceBy%5Bvalue%5D=Story%202
881
841
 
882
842
  - [ ] https://github.com/org/repo/issues/3
883
843
  - [ ] https://github.com/org/repo/issues/4`,
884
- },
885
844
  ],
886
845
  ],
887
846
  expectedUpdateStoryCalls: [
@@ -918,7 +877,7 @@ Some description without checkboxes`,
918
877
  'story2',
919
878
  ],
920
879
  ],
921
- expectedGetIssueByUrlCalls: [
880
+ expectedIssueUrlReads: [
922
881
  ['https://github.com/org/repo/issues/123'],
923
882
  ['https://github.com/org/repo/issues/1'],
924
883
  ['https://github.com/org/repo/issues/2'],
@@ -1010,46 +969,50 @@ Some description without checkboxes`,
1010
969
  [],
1011
970
  ],
1012
971
  ],
1013
- expectedUpdateIssueCalls: [
972
+ expectedUpdateIssueBodyCalls: [
1014
973
  [
1015
974
  {
1016
975
  ...basicStoryIssue1,
1017
976
  org: 'orgA',
1018
977
  repo: 'repoA',
1019
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%201
978
+ body: `- [ ] Task 1`,
979
+ },
980
+ `https://example.com?sliceBy%5Bvalue%5D=Story%201
1020
981
 
1021
982
  - [ ] Task 1`,
1022
- },
1023
983
  ],
1024
984
  [
1025
985
  {
1026
986
  ...basicStoryIssue1,
1027
987
  org: 'orgA',
1028
988
  repo: 'repoA',
1029
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%201
989
+ body: `- [ ] Task 1`,
990
+ },
991
+ `https://example.com?sliceBy%5Bvalue%5D=Story%201
1030
992
 
1031
993
  - [ ] https://github.com/orgA/repoA/issues/1`,
1032
- },
1033
994
  ],
1034
995
  [
1035
996
  {
1036
997
  ...basicStoryIssue2,
1037
998
  org: 'orgB',
1038
999
  repo: 'repoB',
1039
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%202
1000
+ body: `- [ ] Task 2`,
1001
+ },
1002
+ `https://example.com?sliceBy%5Bvalue%5D=Story%202
1040
1003
 
1041
1004
  - [ ] Task 2`,
1042
- },
1043
1005
  ],
1044
1006
  [
1045
1007
  {
1046
1008
  ...basicStoryIssue2,
1047
1009
  org: 'orgB',
1048
1010
  repo: 'repoB',
1049
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%202
1011
+ body: `- [ ] Task 2`,
1012
+ },
1013
+ `https://example.com?sliceBy%5Bvalue%5D=Story%202
1050
1014
 
1051
1015
  - [ ] https://github.com/orgB/repoB/issues/2`,
1052
- },
1053
1016
  ],
1054
1017
  ],
1055
1018
  expectedUpdateStoryCalls: [
@@ -1070,7 +1033,7 @@ Some description without checkboxes`,
1070
1033
  'story2',
1071
1034
  ],
1072
1035
  ],
1073
- expectedGetIssueByUrlCalls: [
1036
+ expectedIssueUrlReads: [
1074
1037
  ['https://github.com/org/repo/issues/123'],
1075
1038
  ['https://github.com/orgA/repoA/issues/1'],
1076
1039
  ['https://github.com/org/repo/issues/456'],
@@ -1093,28 +1056,24 @@ Some description without checkboxes`,
1093
1056
  createTaskFromStoryBodyCheckboxEnabled: false,
1094
1057
  },
1095
1058
  expectedCreateNewIssueCalls: [],
1096
- expectedUpdateIssueCalls: [
1059
+ expectedUpdateIssueBodyCalls: [
1097
1060
  [
1098
- {
1099
- ...basicStoryIssue1,
1100
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%201
1061
+ basicStoryIssue1,
1062
+ `https://example.com?sliceBy%5Bvalue%5D=Story%201
1101
1063
 
1102
1064
  - [ ] Task 1
1103
1065
  - [ ] Task 2`,
1104
- },
1105
1066
  ],
1106
1067
  [
1107
- {
1108
- ...basicStoryIssue2,
1109
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%202
1068
+ basicStoryIssue2,
1069
+ `https://example.com?sliceBy%5Bvalue%5D=Story%202
1110
1070
 
1111
1071
  - [ ] Task 3
1112
1072
  - [ ] Task 4`,
1113
- },
1114
1073
  ],
1115
1074
  ],
1116
1075
  expectedUpdateStoryCalls: [],
1117
- expectedGetIssueByUrlCalls: [
1076
+ expectedIssueUrlReads: [
1118
1077
  ['https://github.com/org/repo/issues/123'],
1119
1078
  ['https://github.com/org/repo/issues/456'],
1120
1079
  ],
@@ -1149,33 +1108,27 @@ Some description without checkboxes`,
1149
1108
  [],
1150
1109
  ],
1151
1110
  ],
1152
- expectedUpdateIssueCalls: [
1111
+ expectedUpdateIssueBodyCalls: [
1153
1112
  [
1154
- {
1155
- ...basicStoryIssue1,
1156
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%201
1113
+ basicStoryIssue1,
1114
+ `https://example.com?sliceBy%5Bvalue%5D=Story%201
1157
1115
 
1158
1116
  - [ ] Task 1
1159
1117
  - [ ] Task 2`,
1160
- },
1161
1118
  ],
1162
1119
  [
1163
- {
1164
- ...basicStoryIssue1,
1165
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%201
1120
+ basicStoryIssue1,
1121
+ `https://example.com?sliceBy%5Bvalue%5D=Story%201
1166
1122
 
1167
1123
  - [ ] https://github.com/org/repo/issues/1
1168
1124
  - [ ] Task 2`,
1169
- },
1170
1125
  ],
1171
1126
  [
1172
- {
1173
- ...basicStoryIssue1,
1174
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%201
1127
+ basicStoryIssue1,
1128
+ `https://example.com?sliceBy%5Bvalue%5D=Story%201
1175
1129
 
1176
1130
  - [ ] https://github.com/org/repo/issues/1
1177
1131
  - [ ] https://github.com/org/repo/issues/2`,
1178
- },
1179
1132
  ],
1180
1133
  ],
1181
1134
  expectedUpdateStoryCalls: [
@@ -1196,7 +1149,7 @@ Some description without checkboxes`,
1196
1149
  'story1',
1197
1150
  ],
1198
1151
  ],
1199
- expectedGetIssueByUrlCalls: [
1152
+ expectedIssueUrlReads: [
1200
1153
  ['https://github.com/org/repo/issues/123'],
1201
1154
  ['https://github.com/org/repo/issues/1'],
1202
1155
  ['https://github.com/org/repo/issues/2'],
@@ -1214,9 +1167,9 @@ Some description without checkboxes`,
1214
1167
  input,
1215
1168
  expectedThrowError,
1216
1169
  expectedCreateNewIssueCalls,
1217
- expectedUpdateIssueCalls,
1170
+ expectedUpdateIssueBodyCalls,
1218
1171
  expectedUpdateStoryCalls,
1219
- expectedGetIssueByUrlCalls,
1172
+ expectedIssueUrlReads,
1220
1173
  expectedAddIssueToProjectCalls,
1221
1174
  }) => {
1222
1175
  it(name, async () => {
@@ -1239,6 +1192,9 @@ Some description without checkboxes`,
1239
1192
  url,
1240
1193
  };
1241
1194
  });
1195
+ mockIssueRepository.getIssueBodyByUrl.mockImplementation(
1196
+ async (url) => storyIssuesByUrl.get(url)?.body ?? null,
1197
+ );
1242
1198
 
1243
1199
  const useCase = new ConvertCheckboxToIssueInStoryIssueUseCase(
1244
1200
  mockIssueRepository,
@@ -1257,14 +1213,18 @@ Some description without checkboxes`,
1257
1213
  expect(mockIssueRepository.createNewIssue.mock.calls).toEqual(
1258
1214
  expectedCreateNewIssueCalls,
1259
1215
  );
1260
- expect(mockIssueRepository.updateIssue.mock.calls).toEqual(
1261
- expectedUpdateIssueCalls,
1216
+ expect(mockIssueRepository.updateIssue).not.toHaveBeenCalled();
1217
+ expect(mockIssueRepository.updateIssueBody.mock.calls).toEqual(
1218
+ expectedUpdateIssueBodyCalls,
1262
1219
  );
1263
1220
  expect(mockIssueRepository.updateStory.mock.calls).toEqual(
1264
1221
  expectedUpdateStoryCalls,
1265
1222
  );
1223
+ expect(mockIssueRepository.getIssueBodyByUrl.mock.calls).toEqual(
1224
+ expectedIssueUrlReads.filter(([url]) => storyIssuesByUrl.has(url)),
1225
+ );
1266
1226
  expect(mockIssueRepository.getIssueByUrl.mock.calls).toEqual(
1267
- expectedGetIssueByUrlCalls,
1227
+ expectedIssueUrlReads.filter(([url]) => !storyIssuesByUrl.has(url)),
1268
1228
  );
1269
1229
  expect(mockIssueRepository.addIssueToProject.mock.calls).toEqual(
1270
1230
  expectedAddIssueToProjectCalls,
@@ -1276,11 +1236,11 @@ Some description without checkboxes`,
1276
1236
  it('skips a story whose story issue was deleted on GitHub and continues with the remaining stories', async () => {
1277
1237
  jest.clearAllMocks();
1278
1238
  const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
1279
- mockIssueRepository.getIssueByUrl.mockImplementation(async (url) => {
1239
+ mockIssueRepository.getIssueBodyByUrl.mockImplementation(async (url) => {
1280
1240
  if (url === basicStoryIssue1.url) {
1281
1241
  return null;
1282
1242
  }
1283
- return basicStoryIssue2;
1243
+ return basicStoryIssue2.body;
1284
1244
  });
1285
1245
  const useCase = new ConvertCheckboxToIssueInStoryIssueUseCase(
1286
1246
  mockIssueRepository,
@@ -1299,28 +1259,26 @@ Some description without checkboxes`,
1299
1259
  expect(warnSpy).toHaveBeenCalledWith(
1300
1260
  expect.stringContaining(basicStoryIssue1.url),
1301
1261
  );
1302
- expect(mockIssueRepository.getIssueByUrl.mock.calls).toEqual([
1262
+ expect(mockIssueRepository.getIssueBodyByUrl.mock.calls).toEqual([
1303
1263
  [basicStoryIssue1.url],
1304
1264
  [basicStoryIssue2.url],
1305
1265
  ]);
1306
- expect(mockIssueRepository.updateIssue.mock.calls).toEqual([
1266
+ expect(mockIssueRepository.updateIssueBody.mock.calls).toEqual([
1307
1267
  [
1308
- {
1309
- ...basicStoryIssue2,
1310
- body: `https://example.com?sliceBy%5Bvalue%5D=Story%202
1268
+ basicStoryIssue2,
1269
+ `https://example.com?sliceBy%5Bvalue%5D=Story%202
1311
1270
 
1312
1271
  - [ ] Task 3
1313
1272
  - [ ] Task 4`,
1314
- },
1315
1273
  ],
1316
1274
  ]);
1317
1275
  warnSpy.mockRestore();
1318
1276
  });
1319
1277
 
1320
- it('propagates errors thrown while fetching the story issue by URL', async () => {
1278
+ it('propagates errors thrown while fetching the story issue body by URL', async () => {
1321
1279
  jest.clearAllMocks();
1322
- const fetchError = new Error('GraphQL request failed');
1323
- mockIssueRepository.getIssueByUrl.mockRejectedValue(fetchError);
1280
+ const fetchError = new Error('Failed to fetch body: 502 Bad Gateway');
1281
+ mockIssueRepository.getIssueBodyByUrl.mockRejectedValue(fetchError);
1324
1282
  const useCase = new ConvertCheckboxToIssueInStoryIssueUseCase(
1325
1283
  mockIssueRepository,
1326
1284
  );
@@ -1335,7 +1293,7 @@ Some description without checkboxes`,
1335
1293
  manager: 'ManagerName',
1336
1294
  createTaskFromStoryBodyCheckboxEnabled: false,
1337
1295
  }),
1338
- ).rejects.toThrow('GraphQL request failed');
1296
+ ).rejects.toThrow('502 Bad Gateway');
1339
1297
  });
1340
1298
 
1341
1299
  describe('story view link position', () => {
@@ -1343,10 +1301,7 @@ Some description without checkboxes`,
1343
1301
 
1344
1302
  const runWithBody = async (body: string): Promise<void> => {
1345
1303
  jest.clearAllMocks();
1346
- mockIssueRepository.getIssueByUrl.mockResolvedValue({
1347
- ...basicStoryIssue1,
1348
- body,
1349
- });
1304
+ mockIssueRepository.getIssueBodyByUrl.mockResolvedValue(body);
1350
1305
  const useCase = new ConvertCheckboxToIssueInStoryIssueUseCase(
1351
1306
  mockIssueRepository,
1352
1307
  );
@@ -1368,8 +1323,8 @@ ${storyViewLink}
1368
1323
 
1369
1324
  - [ ] Task 1`);
1370
1325
 
1371
- expect(mockIssueRepository.updateIssue.mock.calls).toHaveLength(1);
1372
- expect(mockIssueRepository.updateIssue.mock.calls[0][0].body)
1326
+ expect(mockIssueRepository.updateIssueBody.mock.calls).toHaveLength(1);
1327
+ expect(mockIssueRepository.updateIssueBody.mock.calls[0][1])
1373
1328
  .toBe(`${storyViewLink}
1374
1329
 
1375
1330
  Background text
@@ -1384,8 +1339,8 @@ Background text
1384
1339
 
1385
1340
  ${storyViewLink}`);
1386
1341
 
1387
- expect(mockIssueRepository.updateIssue.mock.calls).toHaveLength(1);
1388
- expect(mockIssueRepository.updateIssue.mock.calls[0][0].body)
1342
+ expect(mockIssueRepository.updateIssueBody.mock.calls).toHaveLength(1);
1343
+ expect(mockIssueRepository.updateIssueBody.mock.calls[0][1])
1389
1344
  .toBe(`${storyViewLink}
1390
1345
 
1391
1346
  Background text
@@ -1400,8 +1355,8 @@ Background text
1400
1355
 
1401
1356
  ${storyViewLink}`);
1402
1357
 
1403
- expect(mockIssueRepository.updateIssue.mock.calls).toHaveLength(1);
1404
- expect(mockIssueRepository.updateIssue.mock.calls[0][0].body)
1358
+ expect(mockIssueRepository.updateIssueBody.mock.calls).toHaveLength(1);
1359
+ expect(mockIssueRepository.updateIssueBody.mock.calls[0][1])
1405
1360
  .toBe(`${storyViewLink}
1406
1361
 
1407
1362
  Background text`);
@@ -1412,7 +1367,7 @@ Background text`);
1412
1367
 
1413
1368
  - [ ] Task 1`);
1414
1369
 
1415
- expect(mockIssueRepository.updateIssue).not.toHaveBeenCalled();
1370
+ expect(mockIssueRepository.updateIssueBody).not.toHaveBeenCalled();
1416
1371
  });
1417
1372
  });
1418
1373
 
@@ -1426,10 +1381,7 @@ Background text`);
1426
1381
 
1427
1382
  const runWithBody = async (body: string): Promise<void> => {
1428
1383
  jest.clearAllMocks();
1429
- mockIssueRepository.getIssueByUrl.mockResolvedValue({
1430
- ...basicStoryIssue1,
1431
- body,
1432
- });
1384
+ mockIssueRepository.getIssueBodyByUrl.mockResolvedValue(body);
1433
1385
  const useCase = new ConvertCheckboxToIssueInStoryIssueUseCase(
1434
1386
  mockIssueRepository,
1435
1387
  );
@@ -1451,8 +1403,8 @@ ${earlierStoryViewLink}
1451
1403
 
1452
1404
  - [ ] Task 1`);
1453
1405
 
1454
- expect(mockIssueRepository.updateIssue.mock.calls).toHaveLength(1);
1455
- expect(mockIssueRepository.updateIssue.mock.calls[0][0].body)
1406
+ expect(mockIssueRepository.updateIssueBody.mock.calls).toHaveLength(1);
1407
+ expect(mockIssueRepository.updateIssueBody.mock.calls[0][1])
1456
1408
  .toBe(`${currentStoryViewLink}
1457
1409
 
1458
1410
  - [ ] Task 1`);
@@ -1463,8 +1415,8 @@ ${earlierStoryViewLink}
1463
1415
 
1464
1416
  ${earlierStoryViewLink}`);
1465
1417
 
1466
- expect(mockIssueRepository.updateIssue.mock.calls).toHaveLength(1);
1467
- expect(mockIssueRepository.updateIssue.mock.calls[0][0].body)
1418
+ expect(mockIssueRepository.updateIssueBody.mock.calls).toHaveLength(1);
1419
+ expect(mockIssueRepository.updateIssueBody.mock.calls[0][1])
1468
1420
  .toBe(`${currentStoryViewLink}
1469
1421
 
1470
1422
  Background text`);
@@ -1475,7 +1427,7 @@ Background text`);
1475
1427
 
1476
1428
  ${otherStoryViewLink}`);
1477
1429
 
1478
- expect(mockIssueRepository.updateIssue).not.toHaveBeenCalled();
1430
+ expect(mockIssueRepository.updateIssueBody).not.toHaveBeenCalled();
1479
1431
  });
1480
1432
  });
1481
1433
 
@@ -1493,8 +1445,8 @@ ${otherStoryViewLink}`);
1493
1445
  createTaskFromStoryBodyCheckboxEnabled: boolean,
1494
1446
  ): Promise<void> => {
1495
1447
  jest.clearAllMocks();
1496
- mockIssueRepository.getIssueByUrl.mockResolvedValue(
1497
- storyIssueWithLinkOnFirstLine,
1448
+ mockIssueRepository.getIssueBodyByUrl.mockResolvedValue(
1449
+ storyIssueWithLinkOnFirstLine.body,
1498
1450
  );
1499
1451
  const useCase = new ConvertCheckboxToIssueInStoryIssueUseCase(
1500
1452
  mockIssueRepository,
@@ -1513,14 +1465,14 @@ ${otherStoryViewLink}`);
1513
1465
  it('does not fetch the story issue again when task creation from story body checkboxes is disabled and the story view link is already the first line', async () => {
1514
1466
  await runWithBoardIssue(storyIssueWithLinkOnFirstLine, false);
1515
1467
 
1516
- expect(mockIssueRepository.getIssueByUrl).not.toHaveBeenCalled();
1517
- expect(mockIssueRepository.updateIssue).not.toHaveBeenCalled();
1468
+ expect(mockIssueRepository.getIssueBodyByUrl).not.toHaveBeenCalled();
1469
+ expect(mockIssueRepository.updateIssueBody).not.toHaveBeenCalled();
1518
1470
  });
1519
1471
 
1520
1472
  it('fetches the story issue when task creation from story body checkboxes is enabled', async () => {
1521
1473
  await runWithBoardIssue(storyIssueWithLinkOnFirstLine, true);
1522
1474
 
1523
- expect(mockIssueRepository.getIssueByUrl.mock.calls[0]).toEqual([
1475
+ expect(mockIssueRepository.getIssueBodyByUrl.mock.calls[0]).toEqual([
1524
1476
  storyIssueWithLinkOnFirstLine.url,
1525
1477
  ]);
1526
1478
  });
@@ -1528,9 +1480,68 @@ ${otherStoryViewLink}`);
1528
1480
  it('fetches the story issue when the story view link is missing from the body', async () => {
1529
1481
  await runWithBoardIssue(basicStoryIssue1, false);
1530
1482
 
1531
- expect(mockIssueRepository.getIssueByUrl.mock.calls).toEqual([
1483
+ expect(mockIssueRepository.getIssueBodyByUrl.mock.calls).toEqual([
1484
+ [basicStoryIssue1.url],
1485
+ ]);
1486
+ });
1487
+ });
1488
+
1489
+ describe('story body read and write over REST', () => {
1490
+ const storyViewLink = 'https://example.com?sliceBy%5Bvalue%5D=Story%201';
1491
+ const bodyWithStoryViewLinkOnFirstLine = `${storyViewLink}
1492
+
1493
+ - [ ] Task 1`;
1494
+
1495
+ const runSingleStory = async (
1496
+ createTaskFromStoryBodyCheckboxEnabled = false,
1497
+ ): Promise<void> => {
1498
+ const useCase = new ConvertCheckboxToIssueInStoryIssueUseCase(
1499
+ mockIssueRepository,
1500
+ );
1501
+ await useCase.run({
1502
+ project: singleStoryProject,
1503
+ issues: [basicStoryIssue1],
1504
+ cacheUsed: false,
1505
+ urlOfStoryView: 'https://example.com',
1506
+ storyObjectMap: singleStoryObjectMap,
1507
+ manager: 'ManagerName',
1508
+ createTaskFromStoryBodyCheckboxEnabled,
1509
+ });
1510
+ };
1511
+
1512
+ it('reads the story body through the issue body endpoint and issues no project item query for it', async () => {
1513
+ jest.clearAllMocks();
1514
+ mockIssueRepository.getIssueBodyByUrl.mockResolvedValue(
1515
+ bodyWithStoryViewLinkOnFirstLine,
1516
+ );
1517
+
1518
+ await runSingleStory();
1519
+
1520
+ expect(mockIssueRepository.getIssueBodyByUrl.mock.calls).toEqual([
1532
1521
  [basicStoryIssue1.url],
1533
1522
  ]);
1523
+ expect(mockIssueRepository.getIssueByUrl).not.toHaveBeenCalled();
1524
+ });
1525
+
1526
+ it('writes only the body when the story view link has to be placed on the first line', async () => {
1527
+ jest.clearAllMocks();
1528
+ mockIssueRepository.getIssueBodyByUrl.mockResolvedValue('- [ ] Task 1');
1529
+
1530
+ await runSingleStory();
1531
+
1532
+ expect(mockIssueRepository.updateIssue).not.toHaveBeenCalled();
1533
+ expect(mockIssueRepository.updateIssueBody.mock.calls).toEqual([
1534
+ [
1535
+ expect.objectContaining({
1536
+ org: basicStoryIssue1.org,
1537
+ repo: basicStoryIssue1.repo,
1538
+ number: basicStoryIssue1.number,
1539
+ }),
1540
+ `${storyViewLink}
1541
+
1542
+ - [ ] Task 1`,
1543
+ ],
1544
+ ]);
1534
1545
  });
1535
1546
  });
1536
1547
  });