private void pushMirrorSettings(@Nullable String localPath, @Nullable String remotePath) { client.push(projName, Project.REPO_META, Revision.HEAD, "Add /mirrors.json", Change.ofJsonUpsert("/mirrors.json", "[{" + " \"type\": \"single\"," + " \"direction\": \"REMOTE_TO_LOCAL\"," + " \"localRepo\": \"" + REPO_FOO + "\"," + (localPath != null ? "\"localPath\": \"" + localPath + "\"," : "") + " \"remoteUri\": \"" + gitUri + firstNonNull(remotePath, "") + '"' + "}]")).join(); }
/** * Ensures a {@link RepositoryMetadataException} is raised when the mirror configuration is not valid. */ @Test public void testInvalidMirrors() { // not an array but an object metaRepo.commit(Revision.HEAD, 0, Author.SYSTEM, "", Change.ofJsonUpsert(PATH_MIRRORS, "{}")).join(); assertThatThrownBy(() -> metaRepo.mirrors()).isInstanceOf(RepositoryMetadataException.class); // not an array but a value metaRepo.commit(Revision.HEAD, 0, Author.SYSTEM, "", Change.ofJsonUpsert(PATH_MIRRORS, "\"oops\"")).join(); assertThatThrownBy(() -> metaRepo.mirrors()).isInstanceOf(RepositoryMetadataException.class); // an array that contains null. metaRepo.commit(Revision.HEAD, 0, Author.SYSTEM, "", Change.ofJsonUpsert(PATH_MIRRORS, "[ null ]")).join(); assertThatThrownBy(() -> metaRepo.mirrors()).isInstanceOf(RepositoryMetadataException.class); }
@Test public void testAuth() throws Exception { // Add /credentials.json and /mirrors.json final ArrayNode credentials = JsonNodeFactory.instance.arrayNode().add(credential); client.push(projName, Project.REPO_META, Revision.HEAD, "Add a mirror", Change.ofJsonUpsert("/credentials.json", credentials), Change.ofJsonUpsert("/mirrors.json", "[{" + " \"type\": \"single\"," + " \"direction\": \"REMOTE_TO_LOCAL\"," + " \"localRepo\": \"main\"," + " \"localPath\": \"/\"," + " \"remoteUri\": \"" + gitUri + '"' + "}]")).join(); // Try to perform mirroring to see if authentication works as expected. mirroringService.mirror().join(); } }
@Test public void testJsonPatch_safeReplace() throws JsonProcessingException { final String jsonFilePath = String.format("/test_%s.json", testName.getMethodName()); final Change<JsonNode> change = Change.ofJsonUpsert(jsonFilePath, "{\"key\":1}"); repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, change).join(); final Change<JsonNode> nextChange = Change.ofJsonPatch(jsonFilePath, "{\"key\":2}", "{\"key\":3}"); assertThatThrownBy( () -> repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, nextChange).join()) .isInstanceOf(CompletionException.class) .hasCauseInstanceOf(ChangeConflictException.class); }
private static void commitProjectMetadata(RepositorySupport<ProjectMetadata> metadataRepo, CommandExecutor executor, String projectName, ProjectMetadata metadata) { try { executor.execute(createRepository(author, projectName, Project.REPO_DOGMA)).join(); } catch (Throwable cause) { cause = Exceptions.peel(cause); if (!(cause instanceof RepositoryExistsException)) { Exceptions.throwUnsafely(cause); } } try { metadataRepo.push(projectName, Project.REPO_DOGMA, author, "Add the metadata file", Change.ofJsonUpsert(METADATA_JSON, Jackson.valueToTree(metadata))) .toCompletableFuture().join(); logger.info("Project '{}' has been migrated", projectName); } catch (Throwable cause) { cause = Exceptions.peel(cause); if (!(cause instanceof RedundantChangeException)) { Exceptions.throwUnsafely(cause); } } }
@Override protected void scaffold(CentralDogma client) { client.createProject("directory").join(); client.createRepository("directory", "my-service").join(); client.push("directory", "my-service", Revision.HEAD, "commit", Change.ofJsonUpsert("/endpoint.json", HOST_AND_PORT_LIST_JSON)) .join(); client.push("directory", "my-service", Revision.HEAD, "commit", Change.ofTextUpsert("/endpoints.txt", String.join("\n", HOST_AND_PORT_LIST))) .join(); } };
/** * when the target path or revision is not valid, return an empty map. */ @Test public void testFind_invalidParameter() throws Exception { final String jsonNodePath = "/node.json"; final String jsonString = "{\"key\":\"value\"}"; final Change<JsonNode> jsonChange = Change.ofJsonUpsert(jsonNodePath, jsonString); final Revision revision = repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, jsonChange).join(); assertThatThrownBy(() -> repo.find(new Revision(revision.major() + 1), jsonNodePath).join()) .isInstanceOf(CompletionException.class) .hasCauseInstanceOf(RevisionNotFoundException.class); }
@Before public void revertTestFiles() { final Change<JsonNode> change1 = Change.ofJsonUpsert("/test/test1.json", "[ 1, 2, 3 ]"); final Change<JsonNode> change2 = Change.ofJsonUpsert("/test/test2.json", "{ \"a\": \"apple\" }"); final List<Change<JsonNode>> changes = Arrays.asList(change1, change2); if (!rule.client() .getPreviewDiffs(rule.project(), rule.repo1(), Revision.HEAD, changes) .join().isEmpty()) { rule.client().push( rule.project(), rule.repo1(), Revision.HEAD, "Revert test files", changes).join(); } }
@Test public void testEmptyMirrors() { // should return an empty result when both /credentials.json and /mirrors.json are non-existent. assertThat(metaRepo.mirrors()).isEmpty(); // should return an empty result when /credentials.json exists and /mirrors.json does not. metaRepo.commit(Revision.HEAD, 0, Author.SYSTEM, "", Change.ofJsonUpsert("/credentials.json", "[]")); assertThat(metaRepo.mirrors()).isEmpty(); // should return an empty result when both /credentials.json and /mirrors.json exist. metaRepo.commit(Revision.HEAD, 0, Author.SYSTEM, "", Change.ofJsonUpsert("/mirrors.json", "[]")); assertThat(metaRepo.mirrors()).isEmpty(); }
private static Entry<CommitMessageDto, Change<?>> commitMessageAndChange(AggregatedHttpMessage message) { try { final JsonNode node = Jackson.readTree(message.content().toStringUtf8()); final CommitMessageDto commitMessage = Jackson.convertValue(node.get("commitMessage"), CommitMessageDto.class); final EntryDto file = Jackson.convertValue(node.get("file"), EntryDto.class); final Change<?> change; switch (file.getType()) { case "JSON": change = Change.ofJsonUpsert(file.getPath(), file.getContent()); break; case "TEXT": change = Change.ofTextUpsert(file.getPath(), file.getContent()); break; default: throw new IllegalArgumentException("unsupported file type: " + file.getType()); } return Maps.immutableEntry(commitMessage, change); } catch (IOException e) { throw new IllegalArgumentException("invalid data to be parsed", e); } }
@Test public void testEmptyCommitWithRedundantUpsert2() throws Exception { // Create files to produce redundant changes. final Change<JsonNode> change1 = Change.ofJsonUpsert("/redundant_upsert_2.json", "{ \"foo\": 0, \"bar\": 1 }"); final Change<String> change2 = Change.ofTextUpsert("/redundant_upsert_2.txt", "foo"); repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, change1).join(); repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, change2).join(); // Ensure redundant changes do not count as a valid change. assertThatThrownBy(() -> repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, change1).join()) .isInstanceOf(CompletionException.class) .hasCauseInstanceOf(RedundantChangeException.class); assertThatThrownBy(() -> repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, change2).join()) .isInstanceOf(CompletionException.class) .hasCauseInstanceOf(RedundantChangeException.class); // Ensure a change only whose serialized form is different does not count. final Change<JsonNode> change1a = Change.ofJsonUpsert("/redundant_upsert_2.json", "{ \"bar\": 1, \"foo\": 0 }"); assertThatThrownBy(() -> repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, change1a).join()) .isInstanceOf(CompletionException.class) .hasCauseInstanceOf(RedundantChangeException.class); }
@Override protected void scaffold(CentralDogma client) { super.scaffold(client); for (int i = 0; i < NUM_FILES; i++) { client.push(project(), repo1(), Revision.HEAD, "Put test files", Change.ofJsonUpsert(TEST_ROOT + i + ".json", "{}")).join(); } } };
private void initializeMetadata(long creationTimeMillis, Author author) { // Do not generate a metadata file for internal projects. if (name.equals(INTERNAL_PROJ)) { return; } final Repository dogmaRepo = repos.get(REPO_DOGMA); final Revision headRev = dogmaRepo.normalizeNow(Revision.HEAD); if (!dogmaRepo.exists(headRev, METADATA_JSON).join()) { logger.info("Initializing metadata: {}", name); final UserAndTimestamp userAndTimestamp = UserAndTimestamp.of(author); final RepositoryMetadata repo = new RepositoryMetadata(REPO_META, userAndTimestamp, PerRolePermissions.DEFAULT); final Member member = new Member(author, ProjectRole.OWNER, userAndTimestamp); final ProjectMetadata metadata = new ProjectMetadata(name, ImmutableMap.of(repo.id(), repo), ImmutableMap.of(member.id(), member), ImmutableMap.of(), userAndTimestamp, null); dogmaRepo.commit(headRev, creationTimeMillis, Author.SYSTEM, "Initialize metadata", "", Markup.PLAINTEXT, Change.ofJsonUpsert(METADATA_JSON, Jackson.valueToTree(metadata))).join(); } }
@Test public void testApplyUpsertOnExistingPath() throws Exception { final String jsonPath = "/a_new_json_file.json"; rule.client().push(rule.project(), rule.repo1(), Revision.HEAD, "Add a new JSON file", Change.ofJsonUpsert(jsonPath, "{ \"a\": \"apple\" }")).join(); final Change<JsonNode> change = Change.ofJsonPatch(jsonPath, "{ \"a\": \"apple\" }", "{ \"a\": \"angle\" }"); final List<Change<?>> returnedList = rule.client().getPreviewDiffs(rule.project(), rule.repo1(), Revision.HEAD, change).join(); assertThat(returnedList).hasSize(1); assertThat(returnedList.get(0).type()).isEqualTo(ChangeType.APPLY_JSON_PATCH); } }
@Test public void updateListenerIgnoreDefault() { final CentralDogma client = dogma.client(); final AtomicReference<TestProperty> update = new AtomicReference<>(); client.push("a", "b", Revision.HEAD, "Add c.json", Change.ofJsonUpsert("/c.json", '{' + " \"foo\": 21," + " \"bar\": \"Y\"," + " \"qux\": [\"0\", \"1\"]" + '}')).join(); final TestProperty property = factory.get(new TestProperty(), TestProperty.class, update::set); await().atMost(5, TimeUnit.SECONDS).until(() -> update.get() != null); assertThat(property.getFoo()).isEqualTo(21); assertThat(property.getBar()).isEqualTo("Y"); assertThat(property.getQux()).containsExactly("0", "1"); assertThat(property.getRevision()).isNotNull(); }
@Test public void testWatchRepository() throws Exception { final Revision rev1 = rule.client() .normalizeRevision(rule.project(), rule.repo1(), Revision.HEAD) .join(); final CompletableFuture<Revision> future = rule.client().watchRepository(rule.project(), rule.repo1(), rev1, "/**", 3000); assertThatThrownBy(() -> future.get(500, TimeUnit.MILLISECONDS)).isInstanceOf(TimeoutException.class); final Change<JsonNode> change = Change.ofJsonUpsert("/test/test3.json", "[ 3, 2, 1 ]"); final PushResult result = rule.client().push( rule.project(), rule.repo1(), rev1, "Add test3.json", change).join(); final Revision rev2 = result.revision(); assertThat(rev2).isEqualTo(rev1.forward(1)); assertThat(future.get(3, TimeUnit.SECONDS)).isEqualTo(rev2); }
@Test public void testSingleTypeMirrorWithCredentialId() { metaRepo.commit(Revision.HEAD, 0, Author.SYSTEM, "", Change.ofJsonUpsert( PATH_MIRRORS, "[{" + " \"type\": \"single\"," + " \"direction\": \"LOCAL_TO_REMOTE\"," + " \"localRepo\": \"qux\"," + " \"remoteUri\": \"git+ssh://qux.net/qux.git\"," + " \"credentialId\": \"alice\"" + "}]"), UPSERT_CREDENTIALS).join(); project.repos().create("qux", Author.SYSTEM); final List<Mirror> mirrors = findMirrors(); assertThat(mirrors).hasSize(1); final Mirror m = mirrors.get(0); assertThat(m.localRepo().name()).isEqualTo("qux"); assertThat(m.credential()).isInstanceOf(PasswordMirrorCredential.class); assertThat(((PasswordMirrorCredential) m.credential()).username()).isEqualTo("alice"); }
@Test public void testWatchFileWithIdentityQuery() throws Exception { final Revision rev0 = rule.client() .normalizeRevision(rule.project(), rule.repo1(), Revision.HEAD) .join(); final CompletableFuture<Entry<JsonNode>> future = rule.client().watchFile( rule.project(), rule.repo1(), rev0, Query.ofJson("/test/test1.json"), 3000); assertThatThrownBy(() -> future.get(500, TimeUnit.MILLISECONDS)).isInstanceOf(TimeoutException.class); // An irrelevant change should not trigger a notification. final Change<JsonNode> change1 = Change.ofJsonUpsert("/test/test2.json", "[ 3, 2, 1 ]"); final PushResult res1 = rule.client().push( rule.project(), rule.repo1(), rev0, "Add test2.json", change1).join(); final Revision rev1 = res1.revision(); assertThatThrownBy(() -> future.get(500, TimeUnit.MILLISECONDS)).isInstanceOf(TimeoutException.class); // Make a relevant change now. final Change<JsonNode> change2 = Change.ofJsonUpsert("/test/test1.json", "[ -1, -2, -3 ]"); final PushResult res2 = rule.client().push( rule.project(), rule.repo1(), rev1, "Update test1.json", change2).join(); final Revision rev2 = res2.revision(); assertThat(rev2).isEqualTo(rev0.forward(2)); assertThat(future.get(3, TimeUnit.SECONDS)).isEqualTo( Entry.ofJson(rev2, "/test/test1.json", "[-1,-2,-3]")); }
@Override protected com.linecorp.centraldogma.common.Change<?> doBackward(Change c) { switch (c.getType()) { case UPSERT_JSON: return com.linecorp.centraldogma.common.Change.ofJsonUpsert(c.getPath(), c.getContent()); case UPSERT_TEXT: return com.linecorp.centraldogma.common.Change.ofTextUpsert(c.getPath(), c.getContent()); case REMOVE: return com.linecorp.centraldogma.common.Change.ofRemoval(c.getPath()); case RENAME: return com.linecorp.centraldogma.common.Change.ofRename(c.getPath(), c.getContent()); case APPLY_JSON_PATCH: return com.linecorp.centraldogma.common.Change.ofJsonPatch(c.getPath(), c.getContent()); case APPLY_TEXT_PATCH: return com.linecorp.centraldogma.common.Change.ofTextPatch(c.getPath(), c.getContent()); } throw new Error(); }
@Before public void setUp() throws Exception { prefix = '/' + testName.getMethodName() + '/'; allPattern = prefix + "**"; for (int i = 0; i < NUM_ITERATIONS; i++) { final String jsonPath = prefix + i + ".json"; final String textPath = prefix + i + ".txt"; jsonPaths[i] = jsonPath; textPaths[i] = textPath; jsonUpserts[i] = Change.ofJsonUpsert(jsonPath, "{ \"" + i + "\": " + i + " }"); textUpserts[i] = Change.ofTextUpsert(textPath, "value:\n" + i); } jsonPatches[0] = Change.ofJsonPatch(jsonPaths[0], null, jsonUpserts[0].content()); textPatches[0] = Change.ofTextPatch(textPaths[0], null, textUpserts[0].content()); for (int i = 1; i < NUM_ITERATIONS; i++) { jsonPatches[i] = Change.ofJsonPatch(jsonPaths[0], jsonUpserts[i - 1].content(), jsonUpserts[i].content()); textPatches[i] = Change.ofTextPatch(textPaths[0], textUpserts[i - 1].content(), textUpserts[i].content()); } watchConsumer = null; }