@@ -66,8 +66,11 @@ public function testResourcePurgeDraft() {
6666
6767 // Post, update and publish a dataset with multiple, changing resources.
6868 $ this ->storeDatasetRunQueues ($ id_1 , '1.1 ' , ['1.csv ' , '2.csv ' ], 'post ' );
69+ // These updates reuse 1.csv, so guard against version timestamp collisions.
70+ $ this ->avoidResourceVersionCollision ();
6971 $ this ->storeDatasetRunQueues ($ id_1 , '1.2 ' , ['3.csv ' , '1.csv ' ], 'put ' );
7072 $ this ->getMetastore ()->publish ('dataset ' , $ id_1 );
73+ $ this ->avoidResourceVersionCollision ();
7174 $ this ->storeDatasetRunQueues ($ id_1 , '1.3 ' , ['1.csv ' , '5.csv ' ], 'put ' );
7275
7376 /** @var \Drupal\dkan_common\DatasetInfo $datasetInfo */
@@ -198,75 +201,51 @@ public function testHarvestOrphan() {
198201 }
199202
200203 /**
201- * Test draft moderation workflow with distribution url update and default source resource perspective.
204+ * Provide draft workflow/ perspective combinations .
202205 */
203- public function testDraftWorkflowDistributionUrlSourcePerspective () {
204- // Set resource perspective to source.
205- $ this ->config ('dkan_metastore.settings ' )
206- ->set ('resource_perspective_display ' , DataResource::DEFAULT_SOURCE_PERSPECTIVE )
207- ->save ();
208-
209- $ this ->runDraftWorkflowUpdateDistributionUrl ();
210- }
211-
212- /**
213- * Test draft moderation workflow with distribution url update and local_url source resource perspective.
214- */
215- public function testDraftWorkflowDistributionUrlLocalPerspective () {
216- // Set resource perspective to source.
217- $ this ->config ('dkan_metastore.settings ' )
218- ->set ('resource_perspective_display ' , ResourceLocalizer::LOCAL_URL_PERSPECTIVE )
219- ->save ();
220-
221- $ this ->runDraftWorkflowUpdateDistributionUrl ();
222- }
223-
224- /**
225- * Test draft moderation workflow with modified trigger and default source resource perspective.
226- */
227- public function testDraftWorkflowModifiedTriggerSourcePerspective () {
228- // Set resource perspective to source.
229- $ this ->config ('dkan_metastore.settings ' )
230- ->set ('resource_perspective_display ' , DataResource::DEFAULT_SOURCE_PERSPECTIVE )
231- ->save ();
232-
233- $ this ->runDraftWorkflowModifiedTrigger ();
234- }
235-
236- /**
237- * Test draft moderation workflow with modified trigger and local_url resource perspective.
238- */
239- public function testDraftWorkflowModifiedTriggerLocalPerspective () {
240- // Set resource perspective to local_url.
241- $ this ->config ('dkan_metastore.settings ' )
242- ->set ('resource_perspective_display ' , ResourceLocalizer::LOCAL_URL_PERSPECTIVE )
243- ->save ();
244-
245- $ this ->runDraftWorkflowModifiedTrigger ();
246- }
247-
248- /**
249- * Test draft moderation workflow with distribution title update and source resource perspective.
250- */
251- public function testDraftWorkflowUpdateDistributionTitleSourcePerspective () {
252- // Set resource perspective to local_url.
253- $ this ->config ('dkan_metastore.settings ' )
254- ->set ('resource_perspective_display ' , DataResource::DEFAULT_SOURCE_PERSPECTIVE )
255- ->save ();
256-
257- $ this ->runDraftWorkflowUpdateDistributionTitle ();
206+ public static function draftWorkflowPerspectiveProvider (): array {
207+ $ cases = [
208+ 'url_source ' => [
209+ DataResource::DEFAULT_SOURCE_PERSPECTIVE ,
210+ 'runDraftWorkflowUpdateDistributionUrl ' ,
211+ ],
212+ 'url_local_url ' => [
213+ ResourceLocalizer::LOCAL_URL_PERSPECTIVE ,
214+ 'runDraftWorkflowUpdateDistributionUrl ' ,
215+ ],
216+ 'modified_source ' => [
217+ DataResource::DEFAULT_SOURCE_PERSPECTIVE ,
218+ 'runDraftWorkflowModifiedTrigger ' ,
219+ ],
220+ 'modified_local_url ' => [
221+ ResourceLocalizer::LOCAL_URL_PERSPECTIVE ,
222+ 'runDraftWorkflowModifiedTrigger ' ,
223+ ],
224+ 'title_source ' => [
225+ DataResource::DEFAULT_SOURCE_PERSPECTIVE ,
226+ 'runDraftWorkflowUpdateDistributionTitle ' ,
227+ ],
228+ 'title_local_url ' => [
229+ ResourceLocalizer::LOCAL_URL_PERSPECTIVE ,
230+ 'runDraftWorkflowUpdateDistributionTitle ' ,
231+ ],
232+ ];
233+ return $ cases ;
258234 }
259235
260236 /**
261- * Test draft moderation workflow with distribution title update and local_url resource perspective.
237+ * Test draft workflows across resource perspectives and dist modes.
238+ *
239+ * @dataProvider draftWorkflowPerspectiveProvider
262240 */
263- public function testDraftWorkflowUpdateDistributionTitleLocalPerspective () {
264- // Set resource perspective to local_url.
241+ public function testDraftWorkflowScenarios (
242+ string $ resource_perspective_display ,
243+ string $ workflow_method ,
244+ ): void {
265245 $ this ->config ('dkan_metastore.settings ' )
266- ->set ('resource_perspective_display ' , ResourceLocalizer:: LOCAL_URL_PERSPECTIVE )
246+ ->set ('resource_perspective_display ' , $ resource_perspective_display )
267247 ->save ();
268-
269- $ this ->runDraftWorkflowUpdateDistributionTitle ();
248+ $ this ->{$ workflow_method }();
270249 }
271250
272251 /**
@@ -305,7 +284,9 @@ public function testOrphanDraftDistributionCleanup() {
305284 // Confirm distribution local directory exists.
306285 $ this ->assertDirectoryExists ('public://resources/ ' . $ resourceDirectory );
307286
308- // Update the modified date for the dataset.
287+ // Update the modified date for the dataset. Guard against reusing the same
288+ // resource version timestamp as the original import.
289+ $ this ->avoidResourceVersionCollision ();
309290 $ this ->getMetastore ()->patch ('dataset ' , $ id_1 , json_encode (['modified ' => '06-05-2222 ' ]));
310291
311292 // Simulate datastore_import and cleanup queues post update.
@@ -314,6 +295,7 @@ public function testOrphanDraftDistributionCleanup() {
314295 'datastore_import ' ,
315296 'orphan_reference_processor ' ,
316297 'orphan_resource_remover ' ,
298+ 'resource_purger ' ,
317299 ]);
318300
319301 // Confirm original distribution table removed.
@@ -424,7 +406,10 @@ public function testSanitizeDatasetProperties() {
424406 }
425407
426408 /**
409+ * Post a basic dataset and retrieve it.
427410 *
411+ * @return object
412+ * The retrieved dataset.
428413 */
429414 private function datasetPostAndRetrieve (): object {
430415 $ datasetRootedJsonData = $ this ->getData (123 , 'Test #1 ' , ['district_centerpoints_small.csv ' ]);
@@ -451,7 +436,7 @@ private function datasetPostAndRetrieve(): object {
451436 }
452437
453438 /**
454- *
439+ * Import dataset into datastore perform a basic query.
455440 */
456441 private function datastoreImportAndQuery () {
457442 $ dataset = $ this ->datasetPostAndRetrieve ();
@@ -491,7 +476,13 @@ private function getResourceFromDataset(object $dataset) {
491476 }
492477
493478 /**
479+ * Get the download URL for a resource file.
494480 *
481+ * @param string $filename
482+ * The filename of the resource.
483+ *
484+ * @return string
485+ * The download URL for the resource file from S3 bucket.
495486 */
496487 private function getDownloadUrl (string $ filename ) {
497488 return 'file:// ' . __DIR__ . '/../../files/ ' . $ filename ;
@@ -588,7 +579,10 @@ private function storeDatasetRunQueues(string $identifier, string $title, array
588579 }
589580
590581 /**
582+ * Count the number of datastore tables.
591583 *
584+ * @return int
585+ * The number of datastore tables.
592586 */
593587 private function countTables () {
594588 /** @var \Drupal\Core\Database\Connection $db */
@@ -599,7 +593,10 @@ private function countTables() {
599593 }
600594
601595 /**
596+ * Return normalized CSV filenames found under public://resources.
602597 *
598+ * @return array
599+ * Sorted array of CSV filenames (without paths or prefixes).
603600 */
604601 private function checkFiles () {
605602 /** @var \Drupal\Core\File\FileSystemInterface $fileSystem */
@@ -620,7 +617,10 @@ private function checkFiles() {
620617 }
621618
622619 /**
620+ * Query a resource using the SQL endpoint service.
623621 *
622+ * @param string $queryString
623+ * The SQL-esque query string to run.
624624 */
625625 private function queryResource (string $ queryString ) {
626626 /** @var \Drupal\dkan_datastore\SqlEndpoint\DatastoreSqlEndpointService $sqlEndpoint */
@@ -630,7 +630,17 @@ private function queryResource(string $queryString) {
630630 }
631631
632632 /**
633+ * Handle HTTP verbs for dataset operations.
634+ *
635+ * @param string $method
636+ * The HTTP method to use ('post' or 'put').
637+ * @param \RootedData\RootedJsonData $json
638+ * The JSON data for the dataset.
639+ * @param object $dataset
640+ * The dataset object.
633641 *
642+ * @return string
643+ * The dataset identifier.
634644 */
635645 private function httpVerbHandler (string $ method , RootedJsonData $ json , $ dataset ) {
636646
@@ -648,21 +658,30 @@ private function httpVerbHandler(string $method, RootedJsonData $json, $dataset)
648658 }
649659
650660 /**
661+ * Get the harvester service from the container.
651662 *
663+ * @return \Drupal\dkan_harvest\HarvestService
664+ * The harvester service.
652665 */
653666 private function getHarvester () : HarvestService {
654667 return $ this ->container ->get ('dkan.harvest.service ' );
655668 }
656669
657670 /**
671+ * Get the node storage handler from the container.
658672 *
673+ * @return \Drupal\node\NodeStorage
674+ * The node storage handler.
659675 */
660676 private function getNodeStorage (): NodeStorage {
661677 return $ this ->container ->get ('entity_type.manager ' )->getStorage ('node ' );
662678 }
663679
664680 /**
681+ * Get the metastore service from the container.
665682 *
683+ * @return \Drupal\dkan_metastore\MetastoreService
684+ * The metastore service.
666685 */
667686 private function getMetastore (): MetastoreService {
668687 return $ this ->container ->get ('dkan.metastore.service ' );
@@ -701,11 +720,14 @@ private function createInitialDraftDatasetAndPublish(string $identifier): void {
701720 }
702721
703722 /**
704- * Confirm a new datastore import took place after an update to an existing dataset (draft workflow).
723+ * Confirm draft workflow led to a new datastore import and orphan cleanup.
724+ *
725+ * @param string $identifier
726+ * Dataset identifier.
705727 */
706728 private function confirmNewDatastoreImportDraftWorkflow (string $ identifier ): void {
707- // Simulate all possible queues post update.
708- // Should include datastore_import , orphan_reference_processor and resource_purger.
729+ // Simulate all possible queues post update. Should include datastore
730+ // import , orphan_reference_processor and resource_purger.
709731 $ this ->runQueues ([
710732 'localize_import ' ,
711733 'datastore_import ' ,
@@ -722,7 +744,7 @@ private function confirmNewDatastoreImportDraftWorkflow(string $identifier): voi
722744 $ distributionTablePublished = $ metadata ['published_revision ' ]['distributions ' ][0 ]['table_name ' ] ?? '' ;
723745 $ distributionUuidOld = $ metadata ['published_revision ' ]['distributions ' ][0 ]['distribution_uuid ' ] ?? '' ;
724746
725- // Make sure there are both latest and published versions with different tables.
747+ // Make sure there are latest and published versions with different tables.
726748 $ this ->assertNotEmpty ($ distributionTablePublished , 'Draft revision exists. ' );
727749 $ this ->assertNotEquals ($ distributionTableLatest , $ distributionTablePublished , 'Separate distribution tables exist for latest and published revisions. ' );
728750
@@ -793,6 +815,7 @@ private function runDraftWorkflowModifiedTrigger(): void {
793815 $ this ->getMetastore ()->patch ('dataset ' , $ id_1 , json_encode (['modified ' => '06-05-2222 ' ]));
794816
795817 // Run queues; check that datastore import and orphan cleanup worked as expected.
818+ $ this ->avoidResourceVersionCollision ();
796819 $ this ->confirmNewDatastoreImportDraftWorkflow ($ id_1 );
797820 }
798821
@@ -806,14 +829,14 @@ private function runDraftWorkflowUpdateDistributionTitle(): void {
806829 // Create initial draft dataset and then publish it.
807830 $ this ->createInitialDraftDatasetAndPublish ($ id_1 );
808831
809- // Use same values for distribution as original getData() with updated title .
832+ // Use same values for distribution as original getData() with new filepath .
810833 $ distribution = new \stdClass ();
811834 $ distribution ->title = 'Updated Distribution #0 for ' . $ id_1 ;
812835 $ distribution ->downloadURL = $ this ->getDownloadUrl ('1.csv ' );
813836 $ distribution ->format = 'csv ' ;
814837 $ distribution ->mediaType = 'text/csv ' ;
815838
816- // Run distribution title update with cron run between update and publish events .
839+ // Create a new draft with the new distribution title .
817840 $ this ->runDistributionTitleUpdate ($ id_1 , $ distribution );
818841
819842 // Run distribution title update with cron run only after publish.
@@ -825,6 +848,7 @@ private function runDraftWorkflowUpdateDistributionTitle(): void {
825848 * Separate distribution title update to allow for multiple runs.
826849 */
827850 private function runDistributionTitleUpdate (string $ identifier , \stdClass $ distribution , bool $ skip_cron = FALSE ) {
851+ $ this ->avoidResourceVersionCollision ();
828852 // Create a new draft with the new distribution title.
829853 $ this ->getMetastore ()->patch ('dataset ' , $ identifier , json_encode (
830854 ['distribution ' => [$ distribution ]]
@@ -915,7 +939,7 @@ private function runDraftWorkflowUpdateDistributionUrl(): void {
915939 // Create initial draft dataset and then publish it.
916940 $ this ->createInitialDraftDatasetAndPublish ($ id_1 );
917941
918- // Use same values for distribution as original getData() with new file path .
942+ // Use same values for distribution as original getData() with new filepath .
919943 $ distribution = new \stdClass ();
920944 $ distribution ->title = 'Distribution #0 for ' . $ id_1 ;
921945 $ distribution ->downloadURL = $ this ->getDownloadUrl ('2.csv ' );
@@ -927,8 +951,17 @@ private function runDraftWorkflowUpdateDistributionUrl(): void {
927951 ['distribution ' => [$ distribution ]]
928952 ));
929953
930- // Run queues; check that datastore import and orphan cleanup worked as expected.
954+ // Run queues; check datastore import and orphan cleanup worked as expected.
931955 $ this ->confirmNewDatastoreImportDraftWorkflow ($ id_1 );
932956 }
933957
958+ /**
959+ * Sleep briefly to avoid resource version collisions in draft workflows.
960+ *
961+ * @todo Remove once resource versions are guaranteed unique.
962+ */
963+ private function avoidResourceVersionCollision (): void {
964+ sleep (1 );
965+ }
966+
934967}
0 commit comments