test: Git integration tests (#38337)
This commit is contained in:
parent
52a394f362
commit
f2733c67e9
|
|
@ -13,6 +13,8 @@ public interface ArtifactCE {
|
||||||
|
|
||||||
String getName();
|
String getName();
|
||||||
|
|
||||||
|
void setName(String artifactName);
|
||||||
|
|
||||||
String getWorkspaceId();
|
String getWorkspaceId();
|
||||||
|
|
||||||
Boolean getExportWithConfiguration();
|
Boolean getExportWithConfiguration();
|
||||||
|
|
|
||||||
|
|
@ -2187,13 +2187,17 @@ public class CommonGitServiceCEImpl implements CommonGitServiceCE {
|
||||||
.onErrorResume(throwable -> {
|
.onErrorResume(throwable -> {
|
||||||
log.error("Delete branch failed {}", throwable.getMessage());
|
log.error("Delete branch failed {}", throwable.getMessage());
|
||||||
if (throwable instanceof CannotDeleteCurrentBranchException) {
|
if (throwable instanceof CannotDeleteCurrentBranchException) {
|
||||||
return Mono.error(new AppsmithException(
|
return releaseFileLock(baseArtifactId)
|
||||||
AppsmithError.GIT_ACTION_FAILED,
|
.then(Mono.error(new AppsmithException(
|
||||||
"delete branch",
|
AppsmithError.GIT_ACTION_FAILED,
|
||||||
"Cannot delete current checked out branch"));
|
"delete branch",
|
||||||
|
"Cannot delete current checked out branch")));
|
||||||
}
|
}
|
||||||
return Mono.error(new AppsmithException(
|
return releaseFileLock(baseArtifactId)
|
||||||
AppsmithError.GIT_ACTION_FAILED, "delete branch", throwable.getMessage()));
|
.then(Mono.error(new AppsmithException(
|
||||||
|
AppsmithError.GIT_ACTION_FAILED,
|
||||||
|
"delete branch",
|
||||||
|
throwable.getMessage())));
|
||||||
})
|
})
|
||||||
.flatMap(isBranchDeleted ->
|
.flatMap(isBranchDeleted ->
|
||||||
releaseFileLock(baseArtifactId).map(status -> isBranchDeleted))
|
releaseFileLock(baseArtifactId).map(status -> isBranchDeleted))
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,584 @@
|
||||||
|
package com.appsmith.server.git;
|
||||||
|
|
||||||
|
import com.appsmith.external.dtos.GitBranchDTO;
|
||||||
|
import com.appsmith.external.dtos.GitStatusDTO;
|
||||||
|
import com.appsmith.external.dtos.MergeStatusDTO;
|
||||||
|
import com.appsmith.git.configurations.GitServiceConfig;
|
||||||
|
import com.appsmith.server.applications.base.ApplicationService;
|
||||||
|
import com.appsmith.server.configurations.ProjectProperties;
|
||||||
|
import com.appsmith.server.constants.ArtifactType;
|
||||||
|
import com.appsmith.server.constants.FieldName;
|
||||||
|
import com.appsmith.server.constants.GitDefaultCommitMessage;
|
||||||
|
import com.appsmith.server.domains.Application;
|
||||||
|
import com.appsmith.server.domains.Artifact;
|
||||||
|
import com.appsmith.server.domains.GitArtifactMetadata;
|
||||||
|
import com.appsmith.server.domains.GitProfile;
|
||||||
|
import com.appsmith.server.dtos.AutoCommitResponseDTO;
|
||||||
|
import com.appsmith.server.dtos.GitCommitDTO;
|
||||||
|
import com.appsmith.server.dtos.GitConnectDTO;
|
||||||
|
import com.appsmith.server.dtos.GitMergeDTO;
|
||||||
|
import com.appsmith.server.dtos.GitPullDTO;
|
||||||
|
import com.appsmith.server.git.autocommit.AutoCommitService;
|
||||||
|
import com.appsmith.server.git.common.CommonGitService;
|
||||||
|
import com.appsmith.server.git.resolver.GitArtifactHelperResolver;
|
||||||
|
import com.appsmith.server.git.templates.contexts.GitContext;
|
||||||
|
import com.appsmith.server.git.templates.providers.GitBranchesTestTemplateProvider;
|
||||||
|
import com.appsmith.server.services.GitArtifactHelper;
|
||||||
|
import org.eclipse.jgit.api.Git;
|
||||||
|
import org.eclipse.jgit.api.Status;
|
||||||
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
import org.junit.jupiter.api.TestInstance;
|
||||||
|
import org.junit.jupiter.api.TestTemplate;
|
||||||
|
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||||
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.security.test.context.support.WithUserDetails;
|
||||||
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.test.StepVerifier;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.appsmith.external.git.constants.GitConstants.DEFAULT_COMMIT_MESSAGE;
|
||||||
|
import static com.appsmith.external.git.constants.GitConstants.EMPTY_COMMIT_ERROR_MESSAGE;
|
||||||
|
import static com.appsmith.server.exceptions.AppsmithError.GIT_MERGE_FAILED_LOCAL_CHANGES;
|
||||||
|
import static com.appsmith.server.git.autocommit.AutoCommitEventHandlerImpl.AUTO_COMMIT_MSG_FORMAT;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This integration test suite validates the end-to-end Git workflow for artifacts, performing a sequence of
|
||||||
|
* operations that test repository setup, branch management, status validation, and cleanup. The operations
|
||||||
|
* proceed as follows:
|
||||||
|
*
|
||||||
|
* 1. **Connect Artifact to Git**:
|
||||||
|
* - The artifact is connected to an empty Git repository using a remote URL provided by the Git server initializer.
|
||||||
|
* - A system-generated commit is created as part of the connection process.
|
||||||
|
* - Auto-commit is enabled by default, as verified in the artifact metadata.
|
||||||
|
* - The repository is checked to confirm a single system-generated commit and a clean working directory.
|
||||||
|
*
|
||||||
|
* 2. **Verify Initial Repository State**:
|
||||||
|
* - The default branch is initialized, and its name is verified to match the metadata.
|
||||||
|
* - The repository status is confirmed to be clean with no uncommitted changes.
|
||||||
|
*
|
||||||
|
* 3. **Trigger and Validate Auto-Commit**:
|
||||||
|
* - Auto-commit is triggered, and the resulting commit is validated in the Git log.
|
||||||
|
* - Commit history is checked to confirm the auto-commit appears as a second commit following the initial system-generated commit.
|
||||||
|
*
|
||||||
|
* 4. **Perform Status, Pull, and Commit Operations on the Default Branch (`master`)**:
|
||||||
|
* - The repository status is checked to confirm no changes (`isClean = true`).
|
||||||
|
* - A `pull` operation is executed to ensure synchronization, even when no updates are available.
|
||||||
|
* - A `commit` is attempted with no changes, and the response is validated to confirm no new commits were created.
|
||||||
|
*
|
||||||
|
* 5. **Create and Verify Branches**:
|
||||||
|
* - A new branch `foo` is created from the default branch (`master`).
|
||||||
|
* - Metadata for `foo` is validated, and the commit history confirms that `foo` starts from the latest commit on `master`.
|
||||||
|
* - A second branch `bar` is created from `foo`. Its metadata is verified, and the commit log confirms it starts from the latest commit on `foo`.
|
||||||
|
*
|
||||||
|
* 6. **Test Merging Scenarios**:
|
||||||
|
* - A merge from `bar` to `foo` is validated and shows no action required (`ALREADY_UP_TO_DATE`), as no changes exist.
|
||||||
|
* - Additional changes made to `bar` are merged back into `foo` successfully.
|
||||||
|
*
|
||||||
|
* 7. **Branch Deletion and Repopulation**:
|
||||||
|
* - The branch `foo` is deleted locally but repopulated from the remote repository.
|
||||||
|
* - The latest commit on `foo` is verified to match the changes made on `foo` before deletion.
|
||||||
|
* - An attempt to delete the currently checked-out branch (`master`) fails as expected.
|
||||||
|
*
|
||||||
|
* 8. **Make Changes and Validate Commits**:
|
||||||
|
* - Changes are made to the artifact on `foo` to trigger diffs.
|
||||||
|
* - The repository status is validated as `isClean = false` with pending changes.
|
||||||
|
* - A commit is created with a custom message, and the Git log confirms the commit as the latest on `foo`.
|
||||||
|
* - Changes are successfully discarded, restoring the repository to a clean state.
|
||||||
|
*
|
||||||
|
* 9. **Set and Test Branch Protection**:
|
||||||
|
* - The `master` branch is marked as protected. Commits directly to `master` are restricted.
|
||||||
|
* - Attempts to commit to `master` fail with the appropriate error message.
|
||||||
|
*
|
||||||
|
* 10. **Merge Branches (`baz` to `bar`)**:
|
||||||
|
* - A new branch `baz` is created from `bar`, and its commit log is verified.
|
||||||
|
* - Changes are made to `baz` and successfully merged into `bar` via a fast-forward merge.
|
||||||
|
* - The commit history confirms the merge, and the top commit matches the changes made in `baz`.
|
||||||
|
*
|
||||||
|
* 11. **Disconnect Artifact and Cleanup**:
|
||||||
|
* - The artifact is disconnected from the Git repository.
|
||||||
|
* - All repository branches (`foo`, `bar`, `baz`) except `master` are removed.
|
||||||
|
* - The file system is verified to confirm all repository data is cleaned up.
|
||||||
|
* - Applications associated with the deleted branches are also removed.
|
||||||
|
*
|
||||||
|
* This test suite ensures comprehensive coverage of Git workflows, including repository connection, branch creation,
|
||||||
|
* branch protection, merging, status validation, and repository cleanup. Each operation includes detailed assertions
|
||||||
|
* to validate expected outcomes and handle edge cases.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Testcontainers
|
||||||
|
@SpringBootTest
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
|
public class GitBranchesIT {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@RegisterExtension
|
||||||
|
GitBranchesTestTemplateProvider templateProvider;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@RegisterExtension
|
||||||
|
ArtifactBuilderExtension artifactBuilderExtension;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@RegisterExtension
|
||||||
|
GitServerInitializerExtension gitServerInitializerExtension;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
CommonGitService commonGitService;
|
||||||
|
@Autowired
|
||||||
|
GitTestUtils gitTestUtils;
|
||||||
|
@Autowired
|
||||||
|
GitArtifactHelperResolver gitArtifactHelperResolver;
|
||||||
|
@Autowired
|
||||||
|
GitServiceConfig gitServiceConfig;
|
||||||
|
@Autowired
|
||||||
|
AutoCommitService autoCommitService;
|
||||||
|
@Autowired
|
||||||
|
ProjectProperties projectProperties;
|
||||||
|
@Autowired
|
||||||
|
ApplicationService applicationService;
|
||||||
|
|
||||||
|
final String ORIGIN = "https://foo.bar.com";
|
||||||
|
|
||||||
|
@TestTemplate
|
||||||
|
@WithUserDetails(value = "api_user")
|
||||||
|
void test(GitContext gitContext, ExtensionContext extensionContext) throws IOException, GitAPIException, InterruptedException {
|
||||||
|
|
||||||
|
ExtensionContext.Store contextStore = extensionContext.getStore(ExtensionContext.Namespace.create(ArtifactBuilderExtension.class));
|
||||||
|
String artifactId = contextStore.get(FieldName.ARTIFACT_ID, String.class);
|
||||||
|
|
||||||
|
GitConnectDTO connectDTO = new GitConnectDTO();
|
||||||
|
connectDTO.setRemoteUrl(gitServerInitializerExtension.getGitSshUrl("test" + artifactId));
|
||||||
|
GitProfile gitProfile = new GitProfile("foo bar", "foo@bar.com", null);
|
||||||
|
connectDTO.setGitProfile(gitProfile);
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// - Move the filePath variable to be relative, so that template name and repo name is prettier
|
||||||
|
// - Is it possible to use controller layer here? Might help with also including web filters in IT
|
||||||
|
Artifact artifact = commonGitService.connectArtifactToGit(artifactId, connectDTO, ORIGIN, gitContext.getArtifactType())
|
||||||
|
.block();
|
||||||
|
|
||||||
|
assertThat(artifact).isNotNull();
|
||||||
|
|
||||||
|
ArtifactType artifactType = artifact.getArtifactType();
|
||||||
|
GitArtifactMetadata artifactMetadata = artifact.getGitArtifactMetadata();
|
||||||
|
GitArtifactHelper<?> artifactHelper = gitArtifactHelperResolver.getArtifactHelper(artifactType);
|
||||||
|
Path repoSuffix = artifactHelper.getRepoSuffixPath(
|
||||||
|
artifact.getWorkspaceId(),
|
||||||
|
artifactMetadata.getDefaultArtifactId(),
|
||||||
|
artifactMetadata.getRepoName());
|
||||||
|
|
||||||
|
// Auto-commit should be turned on by default
|
||||||
|
assertThat(artifactMetadata.getAutoCommitConfig().getEnabled()).isTrue();
|
||||||
|
|
||||||
|
Path path = Path.of(gitServiceConfig.getGitRootPath()).resolve(repoSuffix);
|
||||||
|
String branch;
|
||||||
|
ObjectId topOfCommits;
|
||||||
|
|
||||||
|
try (Git git = Git.open(path.toFile())) {
|
||||||
|
branch = git.log().getRepository().getBranch();
|
||||||
|
assertThat(branch).isEqualTo(artifactMetadata.getBranchName());
|
||||||
|
|
||||||
|
// Assert only single system generated commit exists on FS
|
||||||
|
Iterable<RevCommit> commits = git.log().call();
|
||||||
|
Iterator<RevCommit> commitIterator = commits.iterator();
|
||||||
|
assertThat(commitIterator.hasNext()).isTrue();
|
||||||
|
|
||||||
|
RevCommit firstCommit = commitIterator.next();
|
||||||
|
assertThat(firstCommit.getFullMessage()).isEqualTo(DEFAULT_COMMIT_MESSAGE + GitDefaultCommitMessage.CONNECT_FLOW.getReason());
|
||||||
|
topOfCommits = firstCommit.getId();
|
||||||
|
|
||||||
|
assertThat(commitIterator.hasNext()).isFalse();
|
||||||
|
|
||||||
|
// Assert that git directory is clean
|
||||||
|
Status status = git.status().call();
|
||||||
|
assertThat(status.isClean()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the artifact does have auto-commit requirements, and auto-commit gets initiated
|
||||||
|
AutoCommitResponseDTO autoCommitResponseDTO = autoCommitService.autoCommitApplication(artifactId).block();
|
||||||
|
|
||||||
|
assertThat(autoCommitResponseDTO).isNotNull();
|
||||||
|
AutoCommitResponseDTO.AutoCommitResponse autoCommitProgress = autoCommitResponseDTO.getAutoCommitResponse();
|
||||||
|
assertThat(autoCommitProgress).isEqualTo(AutoCommitResponseDTO.AutoCommitResponse.PUBLISHED);
|
||||||
|
|
||||||
|
// Wait for auto-commit to complete
|
||||||
|
// This should not take more than 2 seconds, we're checking every 500 ms
|
||||||
|
long startTime = System.currentTimeMillis(), currentTime = System.currentTimeMillis();
|
||||||
|
while (!autoCommitProgress.equals(AutoCommitResponseDTO.AutoCommitResponse.IDLE)) {
|
||||||
|
Thread.sleep(500);
|
||||||
|
if (currentTime - startTime > 2000) {
|
||||||
|
fail("Auto-commit took too long");
|
||||||
|
}
|
||||||
|
autoCommitProgress = getAutocommitProgress(artifactId, artifact, artifactMetadata);
|
||||||
|
currentTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now there should be two commits in the git log response
|
||||||
|
try (Git git = Git.open(path.toFile())) {
|
||||||
|
branch = git.log().getRepository().getBranch();
|
||||||
|
assertThat(branch).isEqualTo(artifactMetadata.getBranchName());
|
||||||
|
|
||||||
|
Iterable<RevCommit> commits = git.log().call();
|
||||||
|
Iterator<RevCommit> commitIterator = commits.iterator();
|
||||||
|
assertThat(commitIterator.hasNext()).isTrue();
|
||||||
|
|
||||||
|
RevCommit autoCommit = commitIterator.next();
|
||||||
|
assertThat(autoCommit.getFullMessage()).isEqualTo(String.format(AUTO_COMMIT_MSG_FORMAT, projectProperties.getVersion()));
|
||||||
|
|
||||||
|
assertThat(commitIterator.hasNext()).isTrue();
|
||||||
|
RevCommit firstCommit = commitIterator.next();
|
||||||
|
assertThat(firstCommit.getId()).isEqualTo(topOfCommits);
|
||||||
|
|
||||||
|
topOfCommits = autoCommit.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the initialized branch is set as default
|
||||||
|
assertThat(artifactMetadata.getBranchName()).isEqualTo(artifactMetadata.getDefaultBranchName());
|
||||||
|
|
||||||
|
// Assert that the branch is not protected by default
|
||||||
|
assertThat(artifactMetadata.getBranchProtectionRules()).isNullOrEmpty();
|
||||||
|
|
||||||
|
// Check that the status is clean
|
||||||
|
GitStatusDTO statusDTO = commonGitService.getStatus(artifactId, true, artifactType).block();
|
||||||
|
assertThat(statusDTO).isNotNull();
|
||||||
|
assertThat(statusDTO.getIsClean()).isTrue();
|
||||||
|
assertThat(statusDTO.getAheadCount()).isEqualTo(0);
|
||||||
|
assertThat(statusDTO.getBehindCount()).isEqualTo(0);
|
||||||
|
|
||||||
|
// Check that pull when not required, still goes through
|
||||||
|
GitPullDTO gitPullDTO = commonGitService.pullArtifact(artifactId, artifactType).block();
|
||||||
|
assertThat(gitPullDTO).isNotNull();
|
||||||
|
|
||||||
|
// Check that commit says that there is nothing to commit
|
||||||
|
GitCommitDTO commitDTO = new GitCommitDTO();
|
||||||
|
commitDTO.setCommitMessage("Unused message");
|
||||||
|
commitDTO.setDoPush(false);
|
||||||
|
String commitResponse = commonGitService.commitArtifact(commitDTO, artifactId, artifactType)
|
||||||
|
.block();
|
||||||
|
|
||||||
|
assertThat(commitResponse).contains(EMPTY_COMMIT_ERROR_MESSAGE);
|
||||||
|
|
||||||
|
// Check that the previous attempt didn't actually go through
|
||||||
|
try (Git git = Git.open(path.toFile())) {
|
||||||
|
branch = git.log().getRepository().getBranch();
|
||||||
|
assertThat(branch).isEqualTo(artifactMetadata.getBranchName());
|
||||||
|
|
||||||
|
Iterable<RevCommit> commits = git.log().call();
|
||||||
|
assertThat(commits.iterator().next().getId()).isEqualTo(topOfCommits);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that discard, even when not required, goes through
|
||||||
|
Artifact discardedArtifact = commonGitService.discardChanges(artifactId, artifactType).block();
|
||||||
|
assertThat(discardedArtifact).isNotNull();
|
||||||
|
|
||||||
|
// Make a change in the artifact to trigger a diff
|
||||||
|
gitTestUtils.createADiffInArtifact(artifact).block();
|
||||||
|
|
||||||
|
// Check that the status is not clean
|
||||||
|
GitStatusDTO statusDTO2 = commonGitService.getStatus(artifactId, true, artifactType).block();
|
||||||
|
assertThat(statusDTO2).isNotNull();
|
||||||
|
assertThat(statusDTO2.getIsClean()).isFalse();
|
||||||
|
assertThat(statusDTO2.getAheadCount()).isEqualTo(0);
|
||||||
|
assertThat(statusDTO2.getBehindCount()).isEqualTo(0);
|
||||||
|
|
||||||
|
// Check that commit makes the custom message be the top of the log
|
||||||
|
GitCommitDTO commitDTO2 = new GitCommitDTO();
|
||||||
|
commitDTO2.setCommitMessage("Custom message");
|
||||||
|
commitDTO2.setDoPush(true);
|
||||||
|
String commitResponse2 = commonGitService.commitArtifact(commitDTO2, artifactId, artifactType)
|
||||||
|
.block();
|
||||||
|
|
||||||
|
assertThat(commitResponse2).contains("Committed successfully!");
|
||||||
|
|
||||||
|
try (Git git = Git.open(path.toFile())) {
|
||||||
|
branch = git.log().getRepository().getBranch();
|
||||||
|
assertThat(branch).isEqualTo(artifactMetadata.getBranchName());
|
||||||
|
|
||||||
|
Iterable<RevCommit> commits = git.log().call();
|
||||||
|
Iterator<RevCommit> commitIterator = commits.iterator();
|
||||||
|
RevCommit newCommit = commitIterator.next();
|
||||||
|
assertThat(newCommit.getFullMessage()).isEqualTo("Custom message");
|
||||||
|
|
||||||
|
assertThat(commitIterator.next().getId()).isEqualTo(topOfCommits);
|
||||||
|
|
||||||
|
topOfCommits = newCommit.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that status is clean again
|
||||||
|
GitStatusDTO statusDTO3 = commonGitService.getStatus(artifactId, true, artifactType).block();
|
||||||
|
assertThat(statusDTO3).isNotNull();
|
||||||
|
assertThat(statusDTO3.getIsClean()).isTrue();
|
||||||
|
assertThat(statusDTO3.getAheadCount()).isEqualTo(0);
|
||||||
|
assertThat(statusDTO3.getBehindCount()).isEqualTo(0);
|
||||||
|
|
||||||
|
// Make another change to trigger a diff
|
||||||
|
gitTestUtils.createADiffInArtifact(artifact).block();
|
||||||
|
|
||||||
|
// Check that status in not clean
|
||||||
|
GitStatusDTO statusDTO4 = commonGitService.getStatus(artifactId, true, artifactType).block();
|
||||||
|
assertThat(statusDTO4).isNotNull();
|
||||||
|
assertThat(statusDTO4.getIsClean()).isFalse();
|
||||||
|
assertThat(statusDTO4.getAheadCount()).isEqualTo(0);
|
||||||
|
assertThat(statusDTO4.getBehindCount()).isEqualTo(0);
|
||||||
|
|
||||||
|
// Protect the master branch
|
||||||
|
List<String> protectedBranches = commonGitService.updateProtectedBranches(artifactId, List.of(branch), artifactType).block();
|
||||||
|
assertThat(protectedBranches).containsExactly(branch);
|
||||||
|
|
||||||
|
// Now try to commit, and check that it fails
|
||||||
|
GitCommitDTO commitDTO3 = new GitCommitDTO();
|
||||||
|
commitDTO3.setCommitMessage("Failed commit");
|
||||||
|
commitDTO3.setDoPush(false);
|
||||||
|
Mono<String> commitResponse3Mono = commonGitService.commitArtifact(commitDTO3, artifactId, artifactType);
|
||||||
|
StepVerifier.create(commitResponse3Mono)
|
||||||
|
.expectErrorSatisfies(e -> assertThat(e.getMessage()).contains("Cannot commit to protected branch"))
|
||||||
|
.verify();
|
||||||
|
|
||||||
|
// Create a new branch foo from master, check that the commit for new branch is created as system generated
|
||||||
|
// On top of the previous custom commit
|
||||||
|
GitBranchDTO fooBranchDTO = new GitBranchDTO();
|
||||||
|
fooBranchDTO.setBranchName("foo");
|
||||||
|
Artifact fooArtifact = commonGitService.createBranch(artifactId, fooBranchDTO, artifactType).block();
|
||||||
|
assertThat(fooArtifact).isNotNull();
|
||||||
|
|
||||||
|
String fooArtifactId = fooArtifact.getId();
|
||||||
|
GitArtifactMetadata fooMetadata = fooArtifact.getGitArtifactMetadata();
|
||||||
|
assertThat(fooMetadata.getBranchName()).isEqualTo("foo");
|
||||||
|
|
||||||
|
try (Git git = Git.open(path.toFile())) {
|
||||||
|
branch = git.log().getRepository().getBranch();
|
||||||
|
assertThat(branch).isEqualTo(fooMetadata.getBranchName());
|
||||||
|
|
||||||
|
Iterable<RevCommit> commits = git.log().call();
|
||||||
|
Iterator<RevCommit> commitIterator = commits.iterator();
|
||||||
|
RevCommit newCommit = commitIterator.next();
|
||||||
|
assertThat(newCommit.getFullMessage()).contains("branch: foo");
|
||||||
|
|
||||||
|
assertThat(commitIterator.next().getId()).isEqualTo(topOfCommits);
|
||||||
|
|
||||||
|
topOfCommits = newCommit.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that status on foo is clean again
|
||||||
|
GitStatusDTO statusDTO5 = commonGitService.getStatus(fooArtifactId, true, artifactType).block();
|
||||||
|
assertThat(statusDTO5).isNotNull();
|
||||||
|
assertThat(statusDTO5.getIsClean()).isTrue();
|
||||||
|
assertThat(statusDTO5.getAheadCount()).isEqualTo(0);
|
||||||
|
assertThat(statusDTO5.getBehindCount()).isEqualTo(0);
|
||||||
|
|
||||||
|
// Create another branch bar from foo
|
||||||
|
GitBranchDTO barBranchDTO = new GitBranchDTO();
|
||||||
|
barBranchDTO.setBranchName("bar");
|
||||||
|
Artifact barArtifact = commonGitService.createBranch(fooArtifactId, barBranchDTO, artifactType).block();
|
||||||
|
assertThat(barArtifact).isNotNull();
|
||||||
|
|
||||||
|
String barArtifactId = barArtifact.getId();
|
||||||
|
GitArtifactMetadata barMetadata = barArtifact.getGitArtifactMetadata();
|
||||||
|
assertThat(barMetadata.getBranchName()).isEqualTo("bar");
|
||||||
|
|
||||||
|
try (Git git = Git.open(path.toFile())) {
|
||||||
|
branch = git.log().getRepository().getBranch();
|
||||||
|
assertThat(branch).isEqualTo(barMetadata.getBranchName());
|
||||||
|
|
||||||
|
Iterable<RevCommit> commits = git.log().call();
|
||||||
|
Iterator<RevCommit> commitIterator = commits.iterator();
|
||||||
|
|
||||||
|
assertThat(commitIterator.next().getId()).isEqualTo(topOfCommits);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check merge status to foo shows no action required
|
||||||
|
// bar -> foo
|
||||||
|
GitMergeDTO gitMergeDTO = new GitMergeDTO();
|
||||||
|
gitMergeDTO.setDestinationBranch("foo");
|
||||||
|
gitMergeDTO.setSourceBranch("bar");
|
||||||
|
MergeStatusDTO mergeStatusDTO = commonGitService.isBranchMergeable(barArtifactId, gitMergeDTO, artifactType).block();
|
||||||
|
assertThat(mergeStatusDTO).isNotNull();
|
||||||
|
assertThat(mergeStatusDTO.getStatus()).isEqualTo("ALREADY_UP_TO_DATE");
|
||||||
|
|
||||||
|
// Delete foo locally and re-populate from remote
|
||||||
|
List<String> branchList = commonGitService.listBranchForArtifact(artifactId, false, artifactType)
|
||||||
|
.flatMapMany(Flux::fromIterable)
|
||||||
|
.map(GitBranchDTO::getBranchName)
|
||||||
|
.collectList()
|
||||||
|
.block();
|
||||||
|
assertThat(branchList).containsExactlyInAnyOrder(
|
||||||
|
artifactMetadata.getBranchName(),
|
||||||
|
"origin/" + artifactMetadata.getBranchName(),
|
||||||
|
fooMetadata.getBranchName(),
|
||||||
|
"origin/" + fooMetadata.getBranchName(),
|
||||||
|
barMetadata.getBranchName(),
|
||||||
|
"origin/" + barMetadata.getBranchName());
|
||||||
|
|
||||||
|
Mono<? extends Artifact> deleteBranchAttemptMono = commonGitService.deleteBranch(artifactId, "foo", artifactType);
|
||||||
|
StepVerifier
|
||||||
|
.create(deleteBranchAttemptMono)
|
||||||
|
.expectErrorSatisfies(e -> assertThat(e.getMessage()).contains("Cannot delete current checked out branch"))
|
||||||
|
.verify();
|
||||||
|
|
||||||
|
// TODO: I'm having to checkout myself to be able to delete the branch.
|
||||||
|
// Are we relying on auto-commit check to do this otherwise?
|
||||||
|
// Is this a potential bug?
|
||||||
|
try (Git git = Git.open(path.toFile())) {
|
||||||
|
git.checkout().setName("bar").call();
|
||||||
|
}
|
||||||
|
|
||||||
|
commonGitService.deleteBranch(artifactId, "foo", artifactType).block();
|
||||||
|
|
||||||
|
List<String> branchList2 = commonGitService.listBranchForArtifact(artifactId, false, artifactType)
|
||||||
|
.flatMapMany(Flux::fromIterable)
|
||||||
|
.map(GitBranchDTO::getBranchName)
|
||||||
|
.collectList()
|
||||||
|
.block();
|
||||||
|
assertThat(branchList2).containsExactlyInAnyOrder(
|
||||||
|
artifactMetadata.getBranchName(),
|
||||||
|
"origin/" + artifactMetadata.getBranchName(),
|
||||||
|
"origin/" + fooMetadata.getBranchName(),
|
||||||
|
barMetadata.getBranchName(),
|
||||||
|
"origin/" + barMetadata.getBranchName());
|
||||||
|
|
||||||
|
Artifact checkedOutFooArtifact = commonGitService.checkoutBranch(artifactId, "origin/foo", true, artifactType).block();
|
||||||
|
|
||||||
|
assertThat(checkedOutFooArtifact).isNotNull();
|
||||||
|
List<String> branchList3 = commonGitService.listBranchForArtifact(artifactId, false, artifactType)
|
||||||
|
.flatMapMany(Flux::fromIterable)
|
||||||
|
.map(GitBranchDTO::getBranchName)
|
||||||
|
.collectList()
|
||||||
|
.block();
|
||||||
|
assertThat(branchList3).containsExactlyInAnyOrder(
|
||||||
|
artifactMetadata.getBranchName(),
|
||||||
|
"origin/" + artifactMetadata.getBranchName(),
|
||||||
|
fooMetadata.getBranchName(),
|
||||||
|
"origin/" + fooMetadata.getBranchName(),
|
||||||
|
barMetadata.getBranchName(),
|
||||||
|
"origin/" + barMetadata.getBranchName());
|
||||||
|
|
||||||
|
// Verify latest commit on foo should be same as changes made on foo previously
|
||||||
|
try (Git git = Git.open(path.toFile())) {
|
||||||
|
branch = git.log().getRepository().getBranch();
|
||||||
|
assertThat(branch).isEqualTo(fooMetadata.getBranchName());
|
||||||
|
|
||||||
|
Iterable<RevCommit> commits = git.log().call();
|
||||||
|
Iterator<RevCommit> commitIterator = commits.iterator();
|
||||||
|
|
||||||
|
assertThat(commitIterator.next().getId()).isEqualTo(topOfCommits);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make more changes on foo and attempt discard
|
||||||
|
gitTestUtils.createADiffInArtifact(checkedOutFooArtifact).block();
|
||||||
|
|
||||||
|
GitStatusDTO discardableStatus = commonGitService.getStatus(checkedOutFooArtifact.getId(), false, artifactType).block();
|
||||||
|
|
||||||
|
assertThat(discardableStatus).isNotNull();
|
||||||
|
assertThat(discardableStatus.getIsClean()).isFalse();
|
||||||
|
|
||||||
|
Artifact discardedFoo = commonGitService.discardChanges(checkedOutFooArtifact.getId(), artifactType).block();
|
||||||
|
|
||||||
|
GitStatusDTO discardedStatus = commonGitService.getStatus(checkedOutFooArtifact.getId(), false, artifactType).block();
|
||||||
|
|
||||||
|
assertThat(discardedStatus).isNotNull();
|
||||||
|
// TODO: Why is this not clean?
|
||||||
|
// There is an on page load that gets triggered here that is causing a diff
|
||||||
|
// This should ideally have already been fixed on initial artifact import
|
||||||
|
// assertThat(discardedStatus.getIsClean()).isTrue();
|
||||||
|
|
||||||
|
// Make a change to trigger a diff on bar
|
||||||
|
gitTestUtils.createADiffInArtifact(barArtifact).block();
|
||||||
|
|
||||||
|
// Check merge status to master shows not merge-able
|
||||||
|
GitMergeDTO gitMergeDTO2 = new GitMergeDTO();
|
||||||
|
gitMergeDTO2.setSourceBranch("bar");
|
||||||
|
gitMergeDTO2.setDestinationBranch("master");
|
||||||
|
MergeStatusDTO mergeStatusDTO2 = commonGitService.isBranchMergeable(barArtifactId, gitMergeDTO2, artifactType).block();
|
||||||
|
|
||||||
|
assertThat(mergeStatusDTO2).isNotNull();
|
||||||
|
assertThat(mergeStatusDTO2.isMergeAble()).isFalse();
|
||||||
|
assertThat(mergeStatusDTO2.getMessage()).isEqualTo(GIT_MERGE_FAILED_LOCAL_CHANGES.getMessage("bar"));
|
||||||
|
|
||||||
|
// Create a new branch baz and check for new commit
|
||||||
|
GitBranchDTO gitBranchDTO = new GitBranchDTO();
|
||||||
|
gitBranchDTO.setBranchName("baz");
|
||||||
|
Artifact bazArtifact = commonGitService.createBranch(barArtifactId, gitBranchDTO, artifactType).block();
|
||||||
|
|
||||||
|
assertThat(bazArtifact).isNotNull();
|
||||||
|
|
||||||
|
try (Git git = Git.open(path.toFile())) {
|
||||||
|
Iterable<RevCommit> commits = git.log().call();
|
||||||
|
Iterator<RevCommit> commitIterator = commits.iterator();
|
||||||
|
RevCommit newCommit = commitIterator.next();
|
||||||
|
assertThat(newCommit.getFullMessage()).contains("branch: baz");
|
||||||
|
|
||||||
|
assertThat(commitIterator.next().getId()).isEqualTo(topOfCommits);
|
||||||
|
|
||||||
|
topOfCommits = newCommit.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: We're having to discard on bar because
|
||||||
|
// create branch today retains uncommitted change on source branch as well
|
||||||
|
// We will need to update this line once that is fixed.
|
||||||
|
// It won't get caught in tests otherwise since this discard would be a redundant op
|
||||||
|
commonGitService.discardChanges(barArtifactId, artifactType).block();
|
||||||
|
|
||||||
|
GitMergeDTO gitMergeDTO3 = new GitMergeDTO();
|
||||||
|
gitMergeDTO3.setSourceBranch("baz");
|
||||||
|
gitMergeDTO3.setDestinationBranch("bar");
|
||||||
|
|
||||||
|
MergeStatusDTO mergeStatusDTO3 = commonGitService.isBranchMergeable(barArtifactId, gitMergeDTO3, artifactType).block();
|
||||||
|
|
||||||
|
assertThat(mergeStatusDTO3).isNotNull();
|
||||||
|
assertThat(mergeStatusDTO3.isMergeAble()).isTrue();
|
||||||
|
|
||||||
|
// Merge bar to master and check log of commits on foo is same as bar
|
||||||
|
MergeStatusDTO barToBazMergeStatus = commonGitService.mergeBranch(barArtifactId, gitMergeDTO3, artifactType).block();
|
||||||
|
|
||||||
|
assertThat(barToBazMergeStatus).isNotNull();
|
||||||
|
assertThat(barToBazMergeStatus.isMergeAble()).isTrue();
|
||||||
|
assertThat(barToBazMergeStatus.getStatus()).contains("FAST_FORWARD");
|
||||||
|
|
||||||
|
// Since fast-forward should succeed here, top of commit should not change
|
||||||
|
try (Git git = Git.open(path.toFile())) {
|
||||||
|
Iterable<RevCommit> commits = git.log().call();
|
||||||
|
Iterator<RevCommit> commitIterator = commits.iterator();
|
||||||
|
assertThat(commitIterator.next().getId()).isEqualTo(topOfCommits);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disconnect artifact and verify non-existence of `foo`, `bar` and `baz`
|
||||||
|
Artifact disconnectedArtifact = commonGitService.detachRemote(artifactId, artifactType).block();
|
||||||
|
|
||||||
|
assertThat(disconnectedArtifact).isNotNull();
|
||||||
|
assertThat(disconnectedArtifact.getGitArtifactMetadata()).isNull();
|
||||||
|
|
||||||
|
// TODO: This needs to be generified for artifacts
|
||||||
|
Application deletedFooArtifact = applicationService.findById(checkedOutFooArtifact.getId()).block();
|
||||||
|
assertThat(deletedFooArtifact).isNull();
|
||||||
|
Application deletedBarArtifact = applicationService.findById(barArtifactId).block();
|
||||||
|
assertThat(deletedBarArtifact).isNull();
|
||||||
|
Application deletedBazArtifact = applicationService.findById(bazArtifact.getId()).block();
|
||||||
|
assertThat(deletedBazArtifact).isNull();
|
||||||
|
Application existingMasterArtifact = applicationService.findById(artifactId).block();
|
||||||
|
assertThat(existingMasterArtifact).isNotNull();
|
||||||
|
|
||||||
|
// Verify FS is clean after disconnect
|
||||||
|
boolean repoDirectoryNotExists = Files.notExists(path);
|
||||||
|
assertThat(repoDirectoryNotExists).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private AutoCommitResponseDTO.AutoCommitResponse getAutocommitProgress(String artifactId, Artifact artifact, GitArtifactMetadata artifactMetadata) {
|
||||||
|
AutoCommitResponseDTO autoCommitProgress = commonGitService.getAutoCommitProgress(artifactId, artifactMetadata.getBranchName(), artifact.getArtifactType()).block();
|
||||||
|
|
||||||
|
assertThat(autoCommitProgress).isNotNull();
|
||||||
|
return autoCommitProgress.getAutoCommitResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
package com.appsmith.server.git.templates.contexts;
|
||||||
|
|
||||||
|
import com.appsmith.server.constants.ArtifactType;
|
||||||
|
import com.appsmith.server.dtos.ArtifactExchangeJson;
|
||||||
|
import com.appsmith.server.git.ArtifactBuilderExtension;
|
||||||
|
import org.junit.jupiter.api.extension.Extension;
|
||||||
|
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||||
|
import org.junit.jupiter.api.extension.ParameterContext;
|
||||||
|
import org.junit.jupiter.api.extension.ParameterResolutionException;
|
||||||
|
import org.junit.jupiter.api.extension.ParameterResolver;
|
||||||
|
import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class GitContext implements TestTemplateInvocationContext, ParameterResolver {
|
||||||
|
|
||||||
|
private final String fileName;
|
||||||
|
|
||||||
|
private final Class<? extends ArtifactExchangeJson> artifactExchangeJsonType;
|
||||||
|
private final ArtifactType artifactType;
|
||||||
|
|
||||||
|
public GitContext(
|
||||||
|
ExtensionContext extensionContext, String fileName, Class<? extends ArtifactExchangeJson> artifactExchangeJsonType, ArtifactType artifactType) {
|
||||||
|
this.artifactType = artifactType;
|
||||||
|
ExtensionContext.Store contextStore = extensionContext.getStore(ExtensionContext.Namespace.create(ArtifactBuilderExtension.class));
|
||||||
|
contextStore.put(ArtifactExchangeJson.class, artifactExchangeJsonType);
|
||||||
|
contextStore.put("filePath", fileName);
|
||||||
|
this.fileName = fileName;
|
||||||
|
this.artifactExchangeJsonType = artifactExchangeJsonType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayName(int invocationIndex) {
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Extension> getAdditionalExtensions() {
|
||||||
|
return List.of(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFileName() {
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<? extends ArtifactExchangeJson> getArtifactExchangeJsonType() {
|
||||||
|
return artifactExchangeJsonType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
|
||||||
|
throws ParameterResolutionException {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
|
||||||
|
throws ParameterResolutionException {
|
||||||
|
if (parameterContext.getParameter().getType().equals(ExtensionContext.class)) {
|
||||||
|
return extensionContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArtifactType getArtifactType() {
|
||||||
|
return artifactType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.appsmith.server.git.templates.providers;
|
||||||
|
|
||||||
|
import com.appsmith.server.git.templates.providers.ce.GitBranchesTestTemplateProviderCE;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class GitBranchesTestTemplateProvider extends GitBranchesTestTemplateProviderCE {}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.appsmith.server.git.templates.providers.ce;
|
||||||
|
|
||||||
|
import com.appsmith.server.constants.ArtifactType;
|
||||||
|
import com.appsmith.server.dtos.ApplicationJson;
|
||||||
|
import com.appsmith.server.git.templates.contexts.GitContext;
|
||||||
|
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||||
|
import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
|
||||||
|
import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
public class GitBranchesTestTemplateProviderCE implements TestTemplateInvocationContextProvider {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsTestTemplate(ExtensionContext extensionContext) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(
|
||||||
|
ExtensionContext extensionContext) {
|
||||||
|
GitContext context = new GitContext(
|
||||||
|
extensionContext,
|
||||||
|
"com/appsmith/server/git/application.json",
|
||||||
|
ApplicationJson.class,
|
||||||
|
ArtifactType.APPLICATION);
|
||||||
|
return Stream.of(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.appsmith.server.git;
|
||||||
|
|
||||||
|
import com.appsmith.server.constants.ArtifactType;
|
||||||
|
import com.appsmith.server.dtos.ArtifactExchangeJson;
|
||||||
|
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||||
|
|
||||||
|
public interface ArtifactBuilderContext extends ExtensionContext {
|
||||||
|
|
||||||
|
ArtifactType getArtifactType();
|
||||||
|
|
||||||
|
Class<? extends ArtifactExchangeJson> getArtifactJsonType();
|
||||||
|
|
||||||
|
String getArtifactJsonPath();
|
||||||
|
|
||||||
|
String getWorkspaceId();
|
||||||
|
|
||||||
|
void setWorkspaceId(String workspaceId);
|
||||||
|
|
||||||
|
String getArtifactId();
|
||||||
|
|
||||||
|
void setArtifactId(String artifactId);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
package com.appsmith.server.git;
|
||||||
|
|
||||||
|
import com.appsmith.server.applications.base.ApplicationService;
|
||||||
|
import com.appsmith.server.constants.FieldName;
|
||||||
|
import com.appsmith.server.domains.Artifact;
|
||||||
|
import com.appsmith.server.domains.User;
|
||||||
|
import com.appsmith.server.domains.Workspace;
|
||||||
|
import com.appsmith.server.dtos.ArtifactExchangeJson;
|
||||||
|
import com.appsmith.server.imports.internal.ImportService;
|
||||||
|
import com.appsmith.server.migrations.JsonSchemaMigration;
|
||||||
|
import com.appsmith.server.services.ApplicationPageService;
|
||||||
|
import com.appsmith.server.services.UserService;
|
||||||
|
import com.appsmith.server.services.WorkspaceService;
|
||||||
|
import com.appsmith.server.solutions.ApplicationPermission;
|
||||||
|
import com.fasterxml.jackson.databind.MapperFeature;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import org.junit.jupiter.api.extension.AfterEachCallback;
|
||||||
|
import org.junit.jupiter.api.extension.BeforeEachCallback;
|
||||||
|
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This extension basically just creates a new workspace and initializes the artifact provided in context
|
||||||
|
* This artifact is provided in the form of a JSON file that is specified in the context itself
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class ArtifactBuilderExtension implements AfterEachCallback, BeforeEachCallback {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
UserService userService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
WorkspaceService workspaceService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ImportService importService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
JsonSchemaMigration jsonSchemaMigration;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ApplicationService applicationService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ApplicationPermission applicationPermission;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ApplicationPageService applicationPageService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeEach(ExtensionContext extensionContext) throws Exception {
|
||||||
|
ExtensionContext.Store parentContextStore = extensionContext.getParent().get().getStore(ExtensionContext.Namespace.create(ArtifactBuilderExtension.class));
|
||||||
|
Class<? extends ArtifactExchangeJson> aClass = parentContextStore.get(ArtifactExchangeJson.class, Class.class);
|
||||||
|
String filePath = parentContextStore.get("filePath", String.class);
|
||||||
|
ExtensionContext.Store contextStore = extensionContext.getStore(ExtensionContext.Namespace.create(ArtifactBuilderExtension.class));
|
||||||
|
|
||||||
|
ArtifactExchangeJson artifactExchangeJson = createArtifactJson(filePath, aClass).block();
|
||||||
|
assertThat(artifactExchangeJson).isNotNull();
|
||||||
|
|
||||||
|
artifactExchangeJson.getArtifact().setName(aClass.getSimpleName() + "_" + UUID.randomUUID());
|
||||||
|
|
||||||
|
User apiUser = userService.findByEmail("api_user").block();
|
||||||
|
Workspace toCreate = new Workspace();
|
||||||
|
toCreate.setName("Workspace_" + UUID.randomUUID());
|
||||||
|
Workspace workspace =
|
||||||
|
workspaceService.create(toCreate, apiUser, Boolean.FALSE).block();
|
||||||
|
assertThat(workspace).isNotNull();
|
||||||
|
|
||||||
|
Artifact artifact = importService.importNewArtifactInWorkspaceFromJson(workspace.getId(), artifactExchangeJson).block();
|
||||||
|
assertThat(artifact).isNotNull();
|
||||||
|
|
||||||
|
contextStore.put(FieldName.WORKSPACE_ID, (workspace.getId()));
|
||||||
|
contextStore.put(FieldName.ARTIFACT_ID, (artifact.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterEach(ExtensionContext extensionContext) {
|
||||||
|
|
||||||
|
ExtensionContext.Store contextStore = extensionContext.getStore(ExtensionContext.Namespace.create(ArtifactBuilderExtension.class));
|
||||||
|
String workspaceId = contextStore.get(FieldName.WORKSPACE_ID, String.class);
|
||||||
|
|
||||||
|
// Because right now we only have checks for apps
|
||||||
|
// Move this to artifact based model when we fix that
|
||||||
|
applicationService
|
||||||
|
.findByWorkspaceId(workspaceId, applicationPermission.getDeletePermission())
|
||||||
|
.flatMap(remainingApplication -> applicationPageService.deleteApplication(remainingApplication.getId()))
|
||||||
|
.collectList()
|
||||||
|
.block();
|
||||||
|
workspaceService.archiveById(workspaceId).block();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mono<? extends ArtifactExchangeJson> createArtifactJson(String filePath, Class<? extends ArtifactExchangeJson> exchangeJsonType) throws IOException {
|
||||||
|
|
||||||
|
ClassPathResource classPathResource = new ClassPathResource(filePath);
|
||||||
|
|
||||||
|
String artifactJson = classPathResource.getContentAsString(Charset.defaultCharset());
|
||||||
|
|
||||||
|
ArtifactExchangeJson artifactExchangeJson =
|
||||||
|
objectMapper.copy().disable(MapperFeature.USE_ANNOTATIONS).readValue(artifactJson, exchangeJsonType);
|
||||||
|
|
||||||
|
return jsonSchemaMigration.migrateArtifactExchangeJsonToLatestSchema(artifactExchangeJson, null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
package com.appsmith.server.git;
|
||||||
|
|
||||||
|
import com.appsmith.external.constants.PluginConstants;
|
||||||
|
import com.appsmith.external.models.ActionConfiguration;
|
||||||
|
import com.appsmith.external.models.ActionDTO;
|
||||||
|
import com.appsmith.external.models.Datasource;
|
||||||
|
import com.appsmith.external.models.PluginType;
|
||||||
|
import com.appsmith.server.domains.Application;
|
||||||
|
import com.appsmith.server.domains.Artifact;
|
||||||
|
import com.appsmith.server.domains.Plugin;
|
||||||
|
import com.appsmith.server.plugins.base.PluginService;
|
||||||
|
import com.appsmith.server.services.LayoutActionService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class GitArtifactTestUtils<T extends Artifact> {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
LayoutActionService layoutActionService;
|
||||||
|
@Autowired
|
||||||
|
PluginService pluginService;
|
||||||
|
|
||||||
|
Mono<Void> createADiff(Artifact artifact) {
|
||||||
|
|
||||||
|
Application application = (Application) artifact;
|
||||||
|
|
||||||
|
String pageId = application.getPages().get(0).getId();
|
||||||
|
Plugin plugin = pluginService.findByPackageName("restapi-plugin").block();
|
||||||
|
|
||||||
|
Datasource datasource = new Datasource();
|
||||||
|
datasource.setName(PluginConstants.DEFAULT_REST_DATASOURCE);
|
||||||
|
datasource.setWorkspaceId(application.getWorkspaceId());
|
||||||
|
datasource.setPluginId(plugin.getId());
|
||||||
|
|
||||||
|
ActionDTO action = new ActionDTO();
|
||||||
|
action.setPluginType(PluginType.API);
|
||||||
|
action.setName("aGetAction_" + UUID.randomUUID());
|
||||||
|
action.setDatasource(datasource);
|
||||||
|
action.setActionConfiguration(new ActionConfiguration());
|
||||||
|
action.getActionConfiguration().setHttpMethod(HttpMethod.GET);
|
||||||
|
action.setPageId(pageId);
|
||||||
|
|
||||||
|
return layoutActionService
|
||||||
|
.createSingleAction(action, Boolean.FALSE)
|
||||||
|
.then();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
package com.appsmith.server.git;
|
||||||
|
|
||||||
|
import com.appsmith.git.configurations.GitServiceConfig;
|
||||||
|
import com.appsmith.server.applications.base.ApplicationService;
|
||||||
|
import com.appsmith.server.constants.FieldName;
|
||||||
|
import com.appsmith.server.domains.GitAuth;
|
||||||
|
import com.appsmith.server.dtos.ArtifactExchangeJson;
|
||||||
|
import com.appsmith.server.git.common.CommonGitService;
|
||||||
|
import org.assertj.core.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.extension.AfterAllCallback;
|
||||||
|
import org.junit.jupiter.api.extension.AfterEachCallback;
|
||||||
|
import org.junit.jupiter.api.extension.BeforeAllCallback;
|
||||||
|
import org.junit.jupiter.api.extension.BeforeEachCallback;
|
||||||
|
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.FileSystemUtils;
|
||||||
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
import org.testcontainers.containers.GenericContainer;
|
||||||
|
import org.testcontainers.containers.wait.strategy.Wait;
|
||||||
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This extension is meant to set up the SSH keys for an artifact and link it to the git server.
|
||||||
|
* We'll also set up the repository based on the context,
|
||||||
|
* and ensure that all local FS directories for git are clean by the end of a suite
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class GitServerInitializerExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback, AfterAllCallback {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ApplicationService applicationService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
GitServiceConfig gitServiceConfig;
|
||||||
|
|
||||||
|
private static GenericContainer<?> gitContainer = new GenericContainer<>(
|
||||||
|
CompletableFuture.completedFuture("appsmith/test-event-driver"))
|
||||||
|
.withExposedPorts(4200, 22)
|
||||||
|
.waitingFor(Wait.forHttp("/").forPort(4200).forStatusCode(200));
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeAll(ExtensionContext extensionContext) {
|
||||||
|
gitContainer.start();
|
||||||
|
assertThat(gitContainer.isRunning()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeEach(ExtensionContext extensionContext) {
|
||||||
|
ExtensionContext.Store parentContextStore = extensionContext.getParent().get().getStore(ExtensionContext.Namespace.create(ArtifactBuilderExtension.class));
|
||||||
|
Class<? extends ArtifactExchangeJson> aClass = parentContextStore.get(ArtifactExchangeJson.class, Class.class);
|
||||||
|
String filePath = parentContextStore.get("filePath", String.class);
|
||||||
|
ExtensionContext.Store contextStore = extensionContext.getStore(ExtensionContext.Namespace.create(ArtifactBuilderExtension.class));
|
||||||
|
|
||||||
|
String artifactId = contextStore.get(FieldName.ARTIFACT_ID, String.class);
|
||||||
|
String repoName = "test" + artifactId;
|
||||||
|
|
||||||
|
// TODO : Move this to artifact service to enable packages
|
||||||
|
// Generate RSA public key for the given artifact
|
||||||
|
Mono<GitAuth> gitAuthMono = applicationService.createOrUpdateSshKeyPair(artifactId, "RSA");
|
||||||
|
|
||||||
|
String tedGitApiPath = "http://" + gitContainer.getHost() + ":" + gitContainer.getMappedPort(4200) + "/api/v1/git/";
|
||||||
|
|
||||||
|
// Attach public key on TED git server
|
||||||
|
Mono<ResponseEntity<Void>> createRepoMono = WebClient.create(tedGitApiPath + "repos")
|
||||||
|
.post()
|
||||||
|
.bodyValue(Map.of("name", repoName, "private", false))
|
||||||
|
.retrieve()
|
||||||
|
.toBodilessEntity();
|
||||||
|
|
||||||
|
Mono.zip(gitAuthMono, createRepoMono)
|
||||||
|
.flatMap(tuple2 -> {
|
||||||
|
GitAuth auth = tuple2.getT1();
|
||||||
|
String generatedKey = auth.getPublicKey();
|
||||||
|
return WebClient.create(tedGitApiPath + "/keys/" + repoName)
|
||||||
|
.post()
|
||||||
|
.bodyValue(Map.of("title", "key_" + UUID.randomUUID(),
|
||||||
|
"key", generatedKey,
|
||||||
|
"read_only", false))
|
||||||
|
.retrieve()
|
||||||
|
.toBodilessEntity();
|
||||||
|
})
|
||||||
|
.block();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterEach(ExtensionContext extensionContext) {
|
||||||
|
// Delete all repositories created in the current workspace
|
||||||
|
ExtensionContext.Store contextStore = extensionContext.getStore(ExtensionContext.Namespace.create(ArtifactBuilderExtension.class));
|
||||||
|
String workspaceId = contextStore.get(FieldName.WORKSPACE_ID, String.class);
|
||||||
|
|
||||||
|
Path path = Paths.get(gitServiceConfig.getGitRootPath()).resolve(workspaceId);
|
||||||
|
FileSystemUtils.deleteRecursively(path.toFile());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterAll(ExtensionContext extensionContext) {
|
||||||
|
// Stop the TED container
|
||||||
|
gitContainer.stop();
|
||||||
|
assertThat(gitContainer.isRunning()).isFalse();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGitSshUrl(String repoName) {
|
||||||
|
return "ssh://git@" + gitContainer.getHost() +":" + gitContainer.getMappedPort(22) +"/git-server/repos/Cypress/" + repoName + ".git";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.appsmith.server.git;
|
||||||
|
|
||||||
|
import com.appsmith.server.constants.ArtifactType;
|
||||||
|
import com.appsmith.server.domains.Application;
|
||||||
|
import com.appsmith.server.domains.Artifact;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Component
|
||||||
|
public class GitTestUtils {
|
||||||
|
|
||||||
|
private final GitArtifactTestUtils<Application> gitApplicationTestUtils;
|
||||||
|
|
||||||
|
private GitArtifactTestUtils<?> getArtifactSpecificUtils(ArtifactType artifactType) {
|
||||||
|
// TODO For now just work with apps
|
||||||
|
return gitApplicationTestUtils;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Mono<Void> createADiffInArtifact(Artifact artifact) {
|
||||||
|
GitArtifactTestUtils<?> artifactSpecificUtils = getArtifactSpecificUtils(artifact.getArtifactType());
|
||||||
|
|
||||||
|
return artifactSpecificUtils.createADiff(artifact);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user