diff --git a/core/pom.xml b/core/pom.xml
index f9192e4ce..45f2e46da 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -197,6 +197,10 @@
com.google.protobuf
protobuf-java-util
+
+ org.eclipse.jgit
+ org.eclipse.jgit
+
diff --git a/core/src/main/java/org/jsmart/zerocode/core/constants/ZeroCodeReportConstants.java b/core/src/main/java/org/jsmart/zerocode/core/constants/ZeroCodeReportConstants.java
index c296ddc21..8545e65d8 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/constants/ZeroCodeReportConstants.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/constants/ZeroCodeReportConstants.java
@@ -8,7 +8,9 @@ public interface ZeroCodeReportConstants {
String TARGET_REPORT_DIR = "target/zerocode-test-reports/";
String TARGET_FULL_REPORT_CSV_FILE_NAME = "zerocode-junit-granular-report.csv";
String TARGET_FILE_NAME = "target/zerocode-junit-interactive-fuzzy-search.html";
+ String SUREFIRE_REPORT_DIR = "target/surefire-reports/";
String HIGH_CHART_HTML_FILE_NAME = "zerocode_results_chart";
+ String REPORT_UPLOAD_DIR = "target/upload-reports/";
String AUTHOR_MARKER_OLD = "@@"; //Deprecated
String AUTHOR_MARKER_NEW = "@";
String CATEGORY_MARKER = "#";
diff --git a/core/src/main/java/org/jsmart/zerocode/core/di/main/ApplicationMainModule.java b/core/src/main/java/org/jsmart/zerocode/core/di/main/ApplicationMainModule.java
index 0e0cab725..1d5c6ebca 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/di/main/ApplicationMainModule.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/di/main/ApplicationMainModule.java
@@ -27,6 +27,8 @@
import org.jsmart.zerocode.core.engine.validators.ZeroCodeValidatorImpl;
import org.jsmart.zerocode.core.report.ZeroCodeReportGenerator;
import org.jsmart.zerocode.core.report.ZeroCodeReportGeneratorImpl;
+import org.jsmart.zerocode.core.reportsupload.ReportUploader;
+import org.jsmart.zerocode.core.reportsupload.ReportUploaderImpl;
import org.jsmart.zerocode.core.runner.ZeroCodeMultiStepsScenarioRunner;
import org.jsmart.zerocode.core.runner.ZeroCodeMultiStepsScenarioRunnerImpl;
@@ -68,6 +70,7 @@ public void configure() {
bind(ZeroCodeExternalFileProcessor.class).to(ZeroCodeExternalFileProcessorImpl.class);
bind(ZeroCodeParameterizedProcessor.class).to(ZeroCodeParameterizedProcessorImpl.class);
bind(ZeroCodeSorter.class).to(ZeroCodeSorterImpl.class);
+ bind(ReportUploader.class).to(ReportUploaderImpl.class);
// ------------------------------------------------
// Bind properties for localhost, CI, DIT, SIT etc
diff --git a/core/src/main/java/org/jsmart/zerocode/core/engine/listener/TestUtilityListener.java b/core/src/main/java/org/jsmart/zerocode/core/engine/listener/TestUtilityListener.java
index 96142ae7a..e543dc538 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/engine/listener/TestUtilityListener.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/engine/listener/TestUtilityListener.java
@@ -3,6 +3,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.inject.Inject;
import org.jsmart.zerocode.core.report.ZeroCodeReportGenerator;
+import org.jsmart.zerocode.core.reportsupload.ReportUploader;
import org.junit.runner.Description;
import org.junit.runner.Result;
import org.junit.runner.notification.RunListener;
@@ -16,10 +17,13 @@ public class TestUtilityListener extends RunListener {
private final ZeroCodeReportGenerator reportGenerator;
+ private final ReportUploader reportUploader;
+
@Inject
- public TestUtilityListener(ObjectMapper mapper, ZeroCodeReportGenerator injectedReportGenerator) {
+ public TestUtilityListener(ObjectMapper mapper, ZeroCodeReportGenerator injectedReportGenerator, ReportUploader injectedReportUploader) {
this.mapper = mapper;
this.reportGenerator = injectedReportGenerator;
+ this.reportUploader = injectedReportUploader;
}
@Override
@@ -50,9 +54,7 @@ private void printTestCompleted() {
* Override this to handle post-finished tasks
*/
public void runPostFinished() {
- /*
- * Do nothing for now
- */
+ uploadReports();
}
private void generateChartsAndReports() {
@@ -71,4 +73,9 @@ private void generateChartsAndReports() {
reportGenerator.generateExtentReport();
}
+
+ private void uploadReports(){
+ reportUploader.uploadReport();
+ }
+
}
diff --git a/core/src/main/java/org/jsmart/zerocode/core/reportsupload/ReportUploader.java b/core/src/main/java/org/jsmart/zerocode/core/reportsupload/ReportUploader.java
new file mode 100644
index 000000000..19e838062
--- /dev/null
+++ b/core/src/main/java/org/jsmart/zerocode/core/reportsupload/ReportUploader.java
@@ -0,0 +1,5 @@
+package org.jsmart.zerocode.core.reportsupload;
+
+public interface ReportUploader {
+ void uploadReport();
+}
diff --git a/core/src/main/java/org/jsmart/zerocode/core/reportsupload/ReportUploaderImpl.java b/core/src/main/java/org/jsmart/zerocode/core/reportsupload/ReportUploaderImpl.java
new file mode 100644
index 000000000..ecba2605b
--- /dev/null
+++ b/core/src/main/java/org/jsmart/zerocode/core/reportsupload/ReportUploaderImpl.java
@@ -0,0 +1,200 @@
+package org.jsmart.zerocode.core.reportsupload;
+
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
+import org.jsmart.zerocode.core.constants.ZeroCodeReportConstants;
+import org.jsmart.zerocode.core.report.ZeroCodeReportGeneratorImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.file.*;
+
+public class ReportUploaderImpl implements ReportUploader {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ZeroCodeReportGeneratorImpl.class);
+
+ @Inject(optional = true)
+ @Named("reports.repo")
+ private String reportsRepo;
+
+ @Inject(optional = true)
+ @Named("reports.repo.username")
+ private String reportsRepoUsername;
+
+ @Inject(optional = true)
+ @Named("reports.repo.token")
+ private String reportsRepoToken;
+
+ @Inject(optional = true)
+ @Named("reports.repo.max.upload.limit.mb")
+ private Integer reportsRepoMaxUploadLimitMb;
+
+
+ public void uploadReport() {
+ if (!isAllRequiredVariablesSet()) {
+ LOGGER.warn("One or more required variables are not set. Skipping report upload.");
+ return;
+ }
+
+ setDefaultUploadLimit();
+ createParentDirectoryIfNotExists(new File(ZeroCodeReportConstants.REPORT_UPLOAD_DIR));
+
+ try (Git git = initializeOrOpenGitRepository(ZeroCodeReportConstants.REPORT_UPLOAD_DIR)) {
+ addRemoteRepositoryIfMissing(git);
+
+ //Copy files to the repository
+ copyFile(ZeroCodeReportConstants.TARGET_FILE_NAME, ZeroCodeReportConstants.REPORT_UPLOAD_DIR);
+ copyFile(
+ ZeroCodeReportConstants.TARGET_FULL_REPORT_DIR + ZeroCodeReportConstants.TARGET_FULL_REPORT_CSV_FILE_NAME,
+ ZeroCodeReportConstants.REPORT_UPLOAD_DIR);
+
+ copyFirstFileUnder2MBMatchingPattern(
+ ZeroCodeReportConstants.SUREFIRE_REPORT_DIR,
+ "*.xml",
+ ZeroCodeReportConstants.REPORT_UPLOAD_DIR
+ );
+ copyFirstFileUnder2MBMatchingPattern(
+ ZeroCodeReportConstants.TARGET_FULL_REPORT_DIR + "/logs",
+ "*.log",
+ ZeroCodeReportConstants.REPORT_UPLOAD_DIR
+ );
+ addAndCommitChanges(git);
+ pushToRemoteRepository(git);
+ } catch (Exception e) {
+ LOGGER.warn("Report upload failed: {}", e.getMessage());
+ }
+ }
+
+ protected void addRemoteRepositoryIfMissing(Git git) throws URISyntaxException, GitAPIException {
+ if (git.remoteList().call().isEmpty()) {
+ LOGGER.debug("Adding remote repository: {}", reportsRepo);
+ git.remoteAdd().setName("origin").setUri(new URIish(reportsRepo)).call();
+ } else {
+ LOGGER.debug("Remote repository already exists.");
+ }
+ }
+
+ protected void addAndCommitChanges(Git git) throws GitAPIException {
+ git.add().addFilepattern(".").call();
+ LOGGER.debug("Added all files to the Git index.");
+
+ git.commit()
+ .setMessage("Updated files")
+ .setAuthor(reportsRepoUsername, reportsRepoUsername)
+ .call();
+ LOGGER.debug("Committed changes.");
+ }
+
+ protected void pushToRemoteRepository(Git git) throws GitAPIException {
+ git.push()
+ .setCredentialsProvider(new UsernamePasswordCredentialsProvider(reportsRepoUsername, reportsRepoToken))
+ .call();
+ LOGGER.debug("Pushed changes to remote repository!");
+ }
+
+ protected boolean isAllRequiredVariablesSet() {
+ return reportsRepo != null && !reportsRepo.isEmpty() &&
+ reportsRepoUsername != null && !reportsRepoUsername.isEmpty() &&
+ reportsRepoToken != null && !reportsRepoToken.isEmpty();
+ }
+
+ protected void setDefaultUploadLimit() {
+ if (reportsRepoMaxUploadLimitMb == null) {
+ reportsRepoMaxUploadLimitMb = 2;
+ LOGGER.debug("reportsRepoMaxUploadLimitMb is not set. Defaulting to 2 MB.");
+ }
+ }
+
+ protected void createParentDirectoryIfNotExists(File repoDir) {
+ File parentDir = repoDir.getParentFile();
+ if (!parentDir.exists() && parentDir.mkdirs()) {
+ LOGGER.debug("Directory created: {}", parentDir.getAbsolutePath());
+ } else if (!parentDir.exists()) {
+ LOGGER.warn("Failed to create directory: {}", parentDir.getAbsolutePath());
+ }
+ }
+
+ protected Git initializeOrOpenGitRepository(String repoUploadDir) throws IOException, GitAPIException {
+ if (new File(repoUploadDir, ".git").exists()) {
+ LOGGER.debug("Existing Git repository found.");
+ Git git = Git.open(new File(repoUploadDir));
+ LOGGER.debug("Pulling latest changes from remote repository...");
+ git.pull()
+ .setCredentialsProvider(new UsernamePasswordCredentialsProvider(reportsRepoUsername, reportsRepoToken))
+ .call();
+ return git;
+ } else {
+ LOGGER.debug("Initializing a new Git repository...");
+ return Git.cloneRepository()
+ .setURI(reportsRepo)
+ .setDirectory(new File(repoUploadDir))
+ .setCredentialsProvider(new UsernamePasswordCredentialsProvider(reportsRepoUsername, reportsRepoToken))
+ .call();
+ }
+ }
+
+ protected void copyFile(String sourcePath, String targetDirPath) throws IOException {
+ File sourceFile = new File(sourcePath);
+ if (!sourceFile.exists()) {
+ LOGGER.warn("File not found: {}", sourcePath);
+ return;
+ }
+ if (sourceFile.length() <= reportsRepoMaxUploadLimitMb * 1024 * 1024) {
+ Files.copy(sourceFile.toPath(), Paths.get(targetDirPath, sourceFile.getName()), StandardCopyOption.REPLACE_EXISTING);
+ LOGGER.debug("File copied: {}", sourceFile.getName());
+ } else {
+ LOGGER.warn("File size exceeds {} MB. Skipping copy: {}", reportsRepoMaxUploadLimitMb, sourceFile.getName());
+ }
+ }
+
+ protected void copyFirstFileUnder2MBMatchingPattern(String dirPath, String globPattern, String targetDirPath) throws IOException {
+ Path dir = Paths.get(dirPath);
+
+ if (!Files.exists(dir) || !Files.isDirectory(dir)) {
+ LOGGER.warn("Directory does not exist or is not a valid directory: {}", dirPath);
+ return;
+ }
+ final long maxSizeInBytes = reportsRepoMaxUploadLimitMb * 1024 * 1024;
+
+ try (DirectoryStream stream = Files.newDirectoryStream(dir, globPattern)) {
+ for (Path entry : stream) {
+ if (Files.isRegularFile(entry) && Files.size(entry) <= maxSizeInBytes) {
+ Path target = Paths.get(targetDirPath, entry.getFileName().toString());
+ Files.copy(entry, target, StandardCopyOption.REPLACE_EXISTING);
+ LOGGER.info("Copied first matched file: {}", entry.getFileName());
+ return;
+ }
+ }
+ LOGGER.warn("No file under 2MB found matching pattern: {}", globPattern);
+ }
+ }
+
+
+ public void setReportsRepo(String reportsRepo) {
+ this.reportsRepo = reportsRepo;
+ }
+
+ public void setReportsRepoUsername(String reportsRepoUsername) {
+ this.reportsRepoUsername = reportsRepoUsername;
+ }
+
+ public void setReportsRepoToken(String reportsRepoToken) {
+ this.reportsRepoToken = reportsRepoToken;
+ }
+
+ public void setReportsRepoMaxUploadLimitMb(Integer reportsRepoMaxUploadLimitMb) {
+ if (reportsRepoMaxUploadLimitMb == null) {
+ this.reportsRepoMaxUploadLimitMb = 2;
+ }else {
+ this.reportsRepoMaxUploadLimitMb = reportsRepoMaxUploadLimitMb;
+ }
+
+ }
+}
diff --git a/core/src/test/java/org/jsmart/zerocode/core/reportsupload/ReportUploaderImplTest.java b/core/src/test/java/org/jsmart/zerocode/core/reportsupload/ReportUploaderImplTest.java
new file mode 100644
index 000000000..0d19b39e3
--- /dev/null
+++ b/core/src/test/java/org/jsmart/zerocode/core/reportsupload/ReportUploaderImplTest.java
@@ -0,0 +1,126 @@
+package org.jsmart.zerocode.core.reportsupload;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+import org.eclipse.jgit.api.Git;
+import org.jsmart.zerocode.core.di.main.ApplicationMainModule;
+import org.jsmart.zerocode.core.guice.ZeroCodeGuiceTestRule;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class ReportUploaderImplTest {
+ private ReportUploaderImpl reportUploader;
+ private Path tempDir;
+
+ @Inject(optional = true)
+ @Named("reports.repo")
+ private String reportsRepo;
+
+ @Inject(optional = true)
+ @Named("reports.repo.username")
+ private String reportsRepoUsername;
+
+ @Inject(optional = true)
+ @Named("reports.repo.token")
+ private String reportsRepoToken;
+
+ @Inject(optional = true)
+ @Named("reports.repo.max.upload.limit.mb")
+ private Integer reportsRepoMaxUploadLimitMb;
+
+ @Rule
+ public ZeroCodeGuiceTestRule guiceRule = new ZeroCodeGuiceTestRule(this, ReportUploaderImplTest.ZeroCodeTestModule.class);
+
+ public static class ZeroCodeTestModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ ApplicationMainModule applicationMainModule = new ApplicationMainModule("report_uploader.properties");
+ install(applicationMainModule);
+ }
+ }
+
+
+ @Before
+ public void setUp() throws IOException {
+ reportUploader = new ReportUploaderImpl();
+ reportUploader.setDefaultUploadLimit();
+ reportUploader.setReportsRepo(reportsRepo);
+ reportUploader.setReportsRepoUsername(reportsRepoUsername);
+ reportUploader.setReportsRepoToken(reportsRepoToken);
+ reportUploader.setReportsRepoMaxUploadLimitMb(reportsRepoMaxUploadLimitMb);
+ tempDir = Files.createTempDirectory("testRepo");
+ }
+
+ @Test
+ public void testInitializeOrOpenGitRepository_cloneRepo() throws Exception {
+ if(reportUploader.isAllRequiredVariablesSet()){
+ File targetDir = new File(tempDir.toFile(),"clonerepo");
+ Git git = reportUploader.initializeOrOpenGitRepository(targetDir.getAbsolutePath());
+ assertTrue(new File(targetDir, ".git").exists());
+ git.close();
+ }
+ }
+
+ @Test
+ public void testAddAndCommitChanges() throws Exception {
+ if(reportUploader.isAllRequiredVariablesSet()){
+ File targetDir = new File(tempDir.toFile(),"commitrepo");
+ Git git = Git.init().setDirectory(targetDir).call();
+
+ // Create a dummy file to commit
+ File file = new File(targetDir, "dummy.txt");
+ Files.write(file.toPath(), "data".getBytes());
+
+ reportUploader.addAndCommitChanges(git);
+ assertTrue(git.log().call().iterator().hasNext());
+ git.close();
+ }
+ }
+
+ @Test
+ public void copyFile_fileExistsAndWithinSizeLimit_copiesFile() throws IOException {
+ System.out.println(reportsRepo);
+ File sourceFile = new File(tempDir.toFile(), "source.txt");
+ Files.write(sourceFile.toPath(), "test content".getBytes());
+ File targetDir = new File(tempDir.toFile(), "target");
+ targetDir.mkdir();
+
+ reportUploader.copyFile(sourceFile.getAbsolutePath(), targetDir.getAbsolutePath());
+
+ File copiedFile = new File(targetDir, sourceFile.getName());
+ assertTrue(copiedFile.exists());
+ }
+
+ @Test
+ public void copyFile_fileExceedsSizeLimit_logsWarning() throws IOException {
+ File sourceFile = new File(tempDir.toFile(), "largeFile.txt");
+ Files.write(sourceFile.toPath(), new byte[3 * 1024 * 1024]); // 3 MB file
+ File targetDir = new File(tempDir.toFile(), "target");
+ targetDir.mkdir();
+ reportUploader.copyFile(sourceFile.getAbsolutePath(), targetDir.getAbsolutePath());
+
+ File copiedFile = new File(targetDir, sourceFile.getName());
+ assertFalse(copiedFile.exists());
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ if (tempDir != null) {
+ Files.walk(tempDir)
+ .map(Path::toFile)
+ .forEach(File::delete);
+ }
+ }
+
+}
diff --git a/core/src/test/resources/report_uploader.properties b/core/src/test/resources/report_uploader.properties
new file mode 100644
index 000000000..654e12cb1
--- /dev/null
+++ b/core/src/test/resources/report_uploader.properties
@@ -0,0 +1,4 @@
+reports.repo=
+reports.repo.username=
+reports.repo.token=
+reports.repo.max.upload.limit.mb=
\ No newline at end of file
diff --git a/http-testing-examples/src/test/java/org/jsmart/zerocode/testhelp/tests/reportuploader/ReportUploaderTest.java b/http-testing-examples/src/test/java/org/jsmart/zerocode/testhelp/tests/reportuploader/ReportUploaderTest.java
new file mode 100644
index 000000000..988cd687f
--- /dev/null
+++ b/http-testing-examples/src/test/java/org/jsmart/zerocode/testhelp/tests/reportuploader/ReportUploaderTest.java
@@ -0,0 +1,19 @@
+package org.jsmart.zerocode.testhelp.tests.reportuploader;
+
+import org.jsmart.zerocode.core.domain.Scenario;
+import org.jsmart.zerocode.core.domain.TargetEnv;
+import org.jsmart.zerocode.core.runner.ZeroCodeUnitRunner;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertTrue;
+
+@TargetEnv("report_uploader_test.properties")
+@RunWith(ZeroCodeUnitRunner.class)
+public class ReportUploaderTest {
+
+ @Test
+ public void testGet() throws Exception {
+ assertTrue(true);
+ }
+}
diff --git a/http-testing-examples/src/test/resources/report_uploader_test.properties b/http-testing-examples/src/test/resources/report_uploader_test.properties
new file mode 100644
index 000000000..654e12cb1
--- /dev/null
+++ b/http-testing-examples/src/test/resources/report_uploader_test.properties
@@ -0,0 +1,4 @@
+reports.repo=
+reports.repo.username=
+reports.repo.token=
+reports.repo.max.upload.limit.mb=
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 1ad3402e2..e984b4659 100644
--- a/pom.xml
+++ b/pom.xml
@@ -110,6 +110,7 @@
false
3.24.4
+ 5.13.0.202109080827-r
@@ -333,6 +334,11 @@
protobuf-java-util
${google.protobuf.version}
+
+ org.eclipse.jgit
+ org.eclipse.jgit
+ ${eclipse.jgit.version}
+