diff --git a/pom.xml b/pom.xml index 0269fb995..8624aa3ee 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,8 @@ 2.22.1 step.junit.categories.LocalJMeter + + ${maven.multiModuleProjectDirectory}/target/jacoco_analysis/jacoco.exec @@ -791,6 +793,57 @@ + + + + AggregatedJacocoReportEnabled + + + AggregatedJacocoReportEnabled + true + + + + + + + org.jacoco + jacoco-maven-plugin + ${dep.mvn.jacoco.version} + + + ${jacoco.overall.exec} + ${jacoco.overall.exec} + + + + + + + org.jacoco + jacoco-maven-plugin + + + default-prepare-agent + + prepare-agent + + + true + + + + default-report + validate + + report + + + + + + + diff --git a/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AbstractAutomationPackageManagerTest.java b/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AbstractAutomationPackageManagerTest.java index 0da7edcca..8d260ffa0 100644 --- a/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AbstractAutomationPackageManagerTest.java +++ b/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AbstractAutomationPackageManagerTest.java @@ -38,7 +38,7 @@ import step.core.controller.ControllerSettingAccessorImpl; import step.core.dynamicbeans.DynamicBeanResolver; import step.core.dynamicbeans.DynamicValueResolver; -import step.core.objectenricher.ObjectHookRegistry; +import step.core.objectenricher.*; import step.core.plans.Plan; import step.core.plans.PlanAccessorImpl; import step.core.scheduler.ExecutionScheduler; @@ -132,7 +132,13 @@ public void before() { AutomationPackageReaderRegistry automationPackageReaderRegistry = new AutomationPackageReaderRegistry(YamlAutomationPackageVersions.ACTUAL_JSON_SCHEMA_PATH, automationPackageHookRegistry, serializationRegistry); automationPackageReaderRegistry.register(apReader); - this.manager = AutomationPackageManager.createMainAutomationPackageManager( + this.manager = createManager(automationPackageHookRegistry, automationPackageReaderRegistry); + + this.manager.setProvidersResolver(new MockedAutomationPackageProvidersResolver(new HashMap<>(), resourceManager, automationPackageReaderRegistry)); + } + + protected AutomationPackageManager createManager(AutomationPackageHookRegistry automationPackageHookRegistry, AutomationPackageReaderRegistry automationPackageReaderRegistry) { + return AutomationPackageManager.createMainAutomationPackageManager( automationPackageAccessor, functionManager, functionAccessor, @@ -144,8 +150,6 @@ public void before() { null, -1, new ObjectHookRegistry() ); - - this.manager.setProvidersResolver(new MockedAutomationPackageProvidersResolver(new HashMap<>(), resourceManager, automationPackageReaderRegistry)); } @After diff --git a/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AutomationPackageManagerEETest.java b/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AutomationPackageManagerEETest.java index 421f80957..738680f7a 100644 --- a/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AutomationPackageManagerEETest.java +++ b/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AutomationPackageManagerEETest.java @@ -25,6 +25,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import step.attachments.FileResolver; +import step.automation.packages.library.ManagedLibraryProvider; +import step.core.AbstractContext; import step.core.maven.MavenArtifactIdentifier; import step.core.objectenricher.*; import step.repositories.artifact.ResolvedMavenArtifact; @@ -193,11 +195,8 @@ public void testManagedLibrary(){ MockedAutomationPackageProvidersResolver providersResolver = (MockedAutomationPackageProvidersResolver) manager.getProvidersResolver(); providersResolver.getMavenArtifactMocks().put(libVersion1, new ResolvedMavenArtifact(libJar, new SnapshotMetadata("some timestamp", System.currentTimeMillis(), 1, false))); - try (InputStream is = new FileInputStream(automationPackageJar); - InputStream isAnother = new FileInputStream(anotherAutomationPackageJar); - ) { + try (InputStream is = new FileInputStream(automationPackageJar)) { AutomationPackageFileSource sample1ApSource = AutomationPackageFileSource.withInputStream(is, SAMPLE1_FILE_NAME); - AutomationPackageFileSource anotherApSource = AutomationPackageFileSource.withInputStream(isAnother, SAMPLE1_EXTENDED_FILE_NAME); AutomationPackageFileSource libSource = AutomationPackageFileSource.withMavenIdentifier(libVersion1); // 1. Create managed library by Global Admin @@ -279,6 +278,234 @@ public void testManagedLibrary(){ } } + @Test + public void testManagedLibraryInIsolatedProjects() throws IOException, ManagedLibraryMissingException, AutomationPackageReadingException { + File automationPackageJar = new File("src/test/resources/samples/" + SAMPLE1_FILE_NAME); + File anotherAutomationPackageJar = new File("src/test/resources/samples/" + SAMPLE_ECHO_FILE_NAME); + + File libJar = new File("src/test/resources/samples/" + KW_LIB_FILE_NAME); + File libJarUpdated = new File("src/test/resources/samples/" + KW_LIB_FILE_UPDATED_NAME); + + MavenArtifactIdentifier libVersion1 = new MavenArtifactIdentifier("test-group", "test-lib", "1.0.0-SNAPSHOT", null, null); + MockedAutomationPackageProvidersResolver providersResolver = (MockedAutomationPackageProvidersResolver) manager.getProvidersResolver(); + providersResolver.getMavenArtifactMocks().put(libVersion1, new ResolvedMavenArtifact(libJar, new SnapshotMetadata("some timestamp", System.currentTimeMillis(), 1, false))); + + AutomationPackageFileSource libSource = AutomationPackageFileSource.withMavenIdentifier(libVersion1); + + // 1. Create managed library in project1 + AutomationPackageUpdateParameter user1Params = new AutomationPackageUpdateParameterBuilder() + .forJunit() + .withActorUser("user1") + .withEnricher(createTenantEnricher(PROJECT_1)) + .withObjectPredicate(createAccessPredicate(PROJECT_1)) + .withWriteAccessValidator(createWriteAccessValidator(PROJECT_1)) + .build(); + + Resource projectLibResource1 = manager.createAutomationPackageResource(ResourceManager.RESOURCE_TYPE_AP_MANAGED_LIBRARY, libSource, "testManagedLibrary", user1Params); + Assert.assertNotNull(projectLibResource1); + Assert.assertEquals(PROJECT_1, projectLibResource1.getAttribute(ATTRIBUTE_PROJECT_NAME)); + + // 2. Create managed library in project2 with the same name - it is allowed, because we use separate tenants + AutomationPackageUpdateParameter user2Params = new AutomationPackageUpdateParameterBuilder() + .forJunit() + .withActorUser("user2") + .withEnricher(createTenantEnricher(PROJECT_2)) + .withObjectPredicate(createAccessPredicate(PROJECT_2)) + .withWriteAccessValidator(createWriteAccessValidator(PROJECT_2)) + .build(); + + Resource projectLibResource2 = manager.createAutomationPackageResource(ResourceManager.RESOURCE_TYPE_AP_MANAGED_LIBRARY, libSource, "testManagedLibrary", user2Params); + Assert.assertNotNull(projectLibResource2); + Assert.assertEquals(PROJECT_2, projectLibResource2.getAttribute(ATTRIBUTE_PROJECT_NAME)); + + // 3. User1 cannot use the library from project2 + try (InputStream is = new FileInputStream(automationPackageJar)) { + AutomationPackageFileSource sample1ApSource = AutomationPackageFileSource.withInputStream(is, SAMPLE1_FILE_NAME); + try { + AutomationPackageUpdateParameter user1CreateApParams = new AutomationPackageUpdateParameterBuilder() + .forJunit() + .withApSource(sample1ApSource) + .withApLibrarySource(AutomationPackageFileSource.withResourceId(projectLibResource2.getId().toHexString())) + .withAllowUpdate(false) + .withAsync(false) + .withCheckForSameOrigin(true) + .withEnricher(createTenantEnricher(PROJECT_1)) + .withObjectPredicate(createAccessPredicate(PROJECT_1)) + .withWriteAccessValidator(createWriteAccessValidator(PROJECT_1)) + .build(); + + manager.createOrUpdateAutomationPackage(user1CreateApParams); + Assert.fail("Exception should be thrown"); + } catch (AutomationPackageManagerException ex) { + log.info("Exception: {}", ex.getMessage()); + } + } catch (IOException e) { + throw new RuntimeException("IO Exception", e); + } + + AutomationPackageUpdateResult resultAp1; + AutomationPackageUpdateResult resultAp2; + try (InputStream is = new FileInputStream(automationPackageJar); + InputStream isAnother = new FileInputStream(anotherAutomationPackageJar)) { + + // 4. User1 and User2 can create automation packages referencing managed library within their own projects + AutomationPackageFileSource sample1ApSource = AutomationPackageFileSource.withInputStream(is, SAMPLE1_FILE_NAME); + + AutomationPackageUpdateParameter user1CreateApParams = new AutomationPackageUpdateParameterBuilder() + .forJunit() + .withApSource(sample1ApSource) + .withApLibrarySource(AutomationPackageFileSource.withResourceId(projectLibResource1.getId().toHexString())) + .withAllowUpdate(false) + .withAsync(false) + .withCheckForSameOrigin(true) + .withEnricher(createTenantEnricher(PROJECT_1)) + .withObjectPredicate(createAccessPredicate(PROJECT_1)) + .withWriteAccessValidator(createWriteAccessValidator(PROJECT_1)) + .build(); + + resultAp1 = manager.createOrUpdateAutomationPackage(user1CreateApParams); + Assert.assertEquals(CREATED, resultAp1.getStatus()); + AutomationPackageFileSource sample2ApSource = AutomationPackageFileSource.withInputStream(isAnother, SAMPLE1_FILE_NAME); + + AutomationPackageUpdateParameter user2CreateApParams = new AutomationPackageUpdateParameterBuilder() + .forJunit() + .withApSource(sample2ApSource) + .withApLibrarySource(AutomationPackageFileSource.withResourceId(projectLibResource2.getId().toHexString())) + .withAllowUpdate(false) + .withAsync(false) + .withCheckForSameOrigin(true) + .withEnricher(createTenantEnricher(PROJECT_2)) + .withObjectPredicate(createAccessPredicate(PROJECT_2)) + .withWriteAccessValidator(createWriteAccessValidator(PROJECT_2)) + .build(); + + resultAp2 = manager.createOrUpdateAutomationPackage(user2CreateApParams); + Assert.assertEquals(CREATED, resultAp2.getStatus()); + } catch (IOException e) { + throw new RuntimeException("IO Exception", e); + } + + AutomationPackage ap1 = automationPackageAccessor.get(resultAp1.getId()); + AutomationPackage ap2 = automationPackageAccessor.get(resultAp2.getId()); + + // check tenants linked with both APs + Assert.assertEquals(PROJECT_1, ap1.getAttribute(ATTRIBUTE_PROJECT_NAME)); + Assert.assertEquals(PROJECT_2, ap2.getAttribute(ATTRIBUTE_PROJECT_NAME)); + + // check references to libs + Assert.assertEquals(FileResolver.resolveResourceId(ap1.getAutomationPackageLibraryResource()), projectLibResource1.getId().toHexString()); + Assert.assertEquals(FileResolver.resolveResourceId(ap2.getAutomationPackageLibraryResource()), projectLibResource2.getId().toHexString()); + + // 4.1 User1 updates the lib in project1 - AP from project2 should be untouched + providersResolver.getMavenArtifactMocks().put(libVersion1, new ResolvedMavenArtifact( + libJarUpdated, + new SnapshotMetadata("some timestamp", System.currentTimeMillis(), 1, true)) + ); + + Instant nowBeforeLib1Update = Instant.now(); + RefreshResourceResult refreshResourceResult = manager.getAutomationPackageResourceManager().refreshResourceAndLinkedPackages( + projectLibResource1.getId().toHexString(), user1Params, manager + ); + Assert.assertEquals(RefreshResourceResult.ResultStatus.REFRESHED, refreshResourceResult.getResultStatus()); + + // lib1 has been updated + Resource updatedLib1Resource = resourceManager.getResource(projectLibResource1.getId().toHexString()); + Assert.assertFalse(updatedLib1Resource.getLastModificationDate().toInstant().isBefore(nowBeforeLib1Update)); + Assert.assertArrayEquals(Files.readAllBytes(resourceManager.getResourceFile(projectLibResource1.getId().toHexString()).getResourceFile().toPath()), Files.readAllBytes(libJarUpdated.toPath())); + + // lib2 is not updated + Assert.assertFalse(projectLibResource2.getLastModificationDate().toInstant().isAfter(nowBeforeLib1Update)); + Assert.assertArrayEquals(Files.readAllBytes(resourceManager.getResourceFile(projectLibResource2.getId().toHexString()).getResourceFile().toPath()), Files.readAllBytes(libJar.toPath())); + + // take the actual state from db + ap1 = automationPackageAccessor.get(ap1.getId()); + ap2 = automationPackageAccessor.get(ap2.getId()); + + // ap1 has been reuploaded + Assert.assertFalse(ap1.getLastModificationDate().toInstant().isBefore(nowBeforeLib1Update)); + + // ap2 has not been reuploaded + Assert.assertFalse(ap2.getLastModificationDate().toInstant().isAfter(nowBeforeLib1Update)); + + // original tenants for automation packages should not be changed after reupload + Assert.assertEquals(PROJECT_1, ap1.getAttribute(ATTRIBUTE_PROJECT_NAME)); + Assert.assertEquals(PROJECT_2, ap2.getAttribute(ATTRIBUTE_PROJECT_NAME)); + + // 4.2 User1 re-uploads the same managed library again - the behavior should be the same as in 4.1 + providersResolver.getMavenArtifactMocks().put(libVersion1, new ResolvedMavenArtifact( + libJarUpdated, + new SnapshotMetadata("some timestamp 2", System.currentTimeMillis(), 1, true)) + ); + + nowBeforeLib1Update = Instant.now(); + updatedLib1Resource = manager.getAutomationPackageResourceManager().uploadOrReuseAutomationPackageLibrary( + new ManagedLibraryProvider(manager.getAutomationPackageLibraryProvider(libSource, createAccessPredicate(PROJECT_1)), projectLibResource1, "testManagedLibrary"), ap1, user1Params, true, true + ); + + Assert.assertFalse(updatedLib1Resource.getLastModificationDate().toInstant().isBefore(nowBeforeLib1Update)); + Assert.assertArrayEquals(Files.readAllBytes(resourceManager.getResourceFile(projectLibResource1.getId().toHexString()).getResourceFile().toPath()), Files.readAllBytes(libJarUpdated.toPath())); + + // lib2 is not updated + Assert.assertFalse(projectLibResource2.getLastModificationDate().toInstant().isAfter(nowBeforeLib1Update)); + Assert.assertArrayEquals(Files.readAllBytes(resourceManager.getResourceFile(projectLibResource2.getId().toHexString()).getResourceFile().toPath()), Files.readAllBytes(libJar.toPath())); + + // take the actual state from db + ap1 = automationPackageAccessor.get(ap1.getId()); + ap2 = automationPackageAccessor.get(ap2.getId()); + + // original tenants for automation packages should not be changed after reupload + Assert.assertEquals(PROJECT_1, ap1.getAttribute(ATTRIBUTE_PROJECT_NAME)); + Assert.assertEquals(PROJECT_2, ap2.getAttribute(ATTRIBUTE_PROJECT_NAME)); + + // 5. User1 still has the access to AP to read and delete it + AutomationPackage apTakenFromManager = manager.getAutomationPackageById(ap1.getId(), createAccessPredicate(PROJECT_1)); + Assert.assertNotNull(apTakenFromManager); + + manager.removeAutomationPackage(ap1.getId(), "user1", createAccessPredicate(PROJECT_1), createWriteAccessValidator(PROJECT_1)); + + // ap1 doesn't exist anymore + Assert.assertNull(automationPackageAccessor.get(ap1.getId())); + + // resource file and resource for lib are not automatically deleted + ResourceRevisionFileHandle resourceFile = resourceManager.getResourceFile(updatedLib1Resource.getId().toString()); + Assert.assertNotNull(resourceManager.getResource(updatedLib1Resource.getId().toString())); + Assert.assertNotNull(resourceFile); + } + + protected AutomationPackageManager createManager(AutomationPackageHookRegistry automationPackageHookRegistry, AutomationPackageReaderRegistry automationPackageReaderRegistry) { + ObjectHookRegistry objectHookRegistry = new ObjectHookRegistry(); + objectHookRegistry.add(new ObjectHook() { + @Override + public ObjectFilter getObjectFilter(AbstractContext context) { + // TODO: maybe we need to mock the object filter also + return null; + } + + @Override + public ObjectEnricher getObjectEnricher(AbstractContext context) { + return createTenantEnricher(context.get("project") == null ? null : (String) context.get("project")); + } + + @Override + public void rebuildContext(AbstractContext context, EnricheableObject object) throws Exception { + context.put("project", object.getAttribute(ATTRIBUTE_PROJECT_NAME)); + } + }); + + return AutomationPackageManager.createMainAutomationPackageManager( + automationPackageAccessor, + functionManager, + functionAccessor, + planAccessor, + resourceManager, + automationPackageHookRegistry, + automationPackageReaderRegistry, + automationPackageLocks, + null, -1, + objectHookRegistry + ); + } + protected WriteAccessValidator createWriteAccessValidator(String ... projectNames){ return new WriteAccessValidator() { @Override diff --git a/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AutomationPackageManagerOSTest.java b/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AutomationPackageManagerOSTest.java index 8b1545efd..aa18098e4 100644 --- a/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AutomationPackageManagerOSTest.java +++ b/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AutomationPackageManagerOSTest.java @@ -47,6 +47,7 @@ import java.nio.file.Files; import java.time.Duration; import java.util.*; +import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.stream.Collectors; diff --git a/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AutomationPackageReaderTest.java b/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AutomationPackageReaderTest.java index 7ef07307d..046266aa8 100644 --- a/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AutomationPackageReaderTest.java +++ b/step-automation-packages/step-automation-packages-controller/src/test/java/step/automation/packages/AutomationPackageReaderTest.java @@ -4,6 +4,7 @@ import ch.exense.commons.io.FileHelper; import jakarta.json.spi.JsonProvider; import org.apache.commons.collections.CollectionUtils; +import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; import org.slf4j.Logger; @@ -254,4 +255,25 @@ public void testInvalidAPNames() { } } + @Test + public void testMissingDescriptor() throws IOException, AutomationPackageReadingException { + File tempFolder = FileHelper.createTempFolder(); + FileHelper.unzip(this.getClass().getClassLoader().getResourceAsStream("step/automation/packages/step-automation-packages.zip"), tempFolder); + + // remove descriptor - reader will use the folder name as AP name and autoscan to lookup existing plans in this folder + File descriptor = new File(tempFolder, "automation-package.yaml"); + boolean deleteOk = descriptor.delete(); + Assert.assertTrue(deleteOk); + + AutomationPackageContent automationPackageContent = reader.readAutomationPackageFromJarFile(tempFolder, null, null); + assertNotNull(automationPackageContent); + assertEquals(tempFolder.getName(), automationPackageContent.getName()); + + List plans = automationPackageContent.getPlans(); + assertEquals(0, plans.size()); + + // cleanup temp folder + Assert.assertTrue("Temp folder cannot be removed", FileHelper.deleteFolder(tempFolder)); + } + } \ No newline at end of file