return Change.ofTextUpsert(path, node.get("content").textValue()); return Change.ofJsonUpsert(path, node.get("content")); return Change.ofRemoval(path); return Change.ofRename(path, node.get("content").textValue()); return Change.ofTextPatch(path, node.get("content").textValue()); return Change.ofJsonPatch(path, node.get("content"));
@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; }
@Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof Change)) { return false; } final Change<?> that = (Change<?>) o; if (type != that.type()) { return false; } if (!path.equals(that.path())) { return false; } return Objects.equals(content, that.content()); }
@Override int weigh(Map<String, Change<?>> value) { int weight = 0; weight += pathPattern.length(); for (Change<?> e : value.values()) { weight += e.path().length(); final String content = e.contentAsText(); if (content != null) { weight += content.length(); } } return weight; }
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); } }
/** * Restores a {@link ProjectMetadata} whose name equals to the specified {@code projectName}. */ public CompletableFuture<Revision> restoreProject(Author author, String projectName) { requireNonNull(author, "author"); requireNonNull(projectName, "projectName"); final Change<JsonNode> change = Change.ofJsonPatch(METADATA_JSON, new RemoveOperation(PROJECT_REMOVAL).toJsonNode()); return metadataRepo.push(projectName, Project.REPO_DOGMA, author, "Restore the project: " + projectName, change); }
@Test public void testDiff_rename() throws Exception { final CentralDogma client = rule.client(); final Revision rev1 = client.push(rule.project(), rule.repo1(), HEAD, "summary1", Change.ofTextUpsert("/bar.txt", "hello")).join().revision(); final Revision rev2 = client.push(rule.project(), rule.repo1(), HEAD, "summary2", Change.ofRename("/bar.txt", "/baz.txt")).join().revision(); assertThat(rev1.forward(1)).isEqualTo(rev2); assertThat(client.getDiff(rule.project(), rule.repo1(), rev1, rev2, Query.ofText("/bar.txt")).join()) .isEqualTo(Change.ofRemoval("/bar.txt")); } }
@Test public void testRename() throws Exception { repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, jsonUpserts[0]).join(); // Rename without content modification. repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, Change.ofRename(jsonPaths[0], jsonPaths[1])).join(); assertThat(repo.exists(HEAD, jsonPaths[0]).join()).isFalse(); assertThat(repo.exists(HEAD, jsonPaths[1]).join()).isTrue(); assertThat(repo.exists(HEAD, jsonPaths[2]).join()).isFalse(); assertThatJson(repo.get(HEAD, jsonPaths[1]).join().content()) .isEqualTo(jsonUpserts[0].content()); // Rename with content modification. repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, Change.ofRename(jsonPaths[1], jsonPaths[2]), Change.ofJsonPatch(jsonPaths[2], jsonPatches[1].content()), Change.ofJsonPatch(jsonPaths[2], jsonPatches[2].content())).join(); assertThat(repo.exists(HEAD, jsonPaths[0]).join()).isFalse(); assertThat(repo.exists(HEAD, jsonPaths[1]).join()).isFalse(); assertThat(repo.exists(HEAD, jsonPaths[2]).join()).isTrue(); assertThatJson(repo.get(HEAD, jsonPaths[2]).join().content()) .isEqualTo(jsonUpserts[2].content()); }
@Test public void testWatchRemoval() throws Exception { final String path = jsonPaths[0]; final Change<JsonNode> upsert1 = Change.ofJsonUpsert(path, "1"); final Change<JsonNode> upsert2 = Change.ofJsonUpsert(path, "2"); final Revision rev1 = repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, upsert1).join(); final CompletableFuture<Entry<JsonNode>> f = repo.watch(rev1, Query.ofJson(path)); // Remove the file being watched. repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, Change.ofRemoval(path)).join(); // Should wait patiently until the file reappears. assertThatThrownBy(() -> f.get(1, TimeUnit.SECONDS)).isInstanceOf(TimeoutException.class); // Add the file back again without changing the content. repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, upsert1).join(); // Should wait patiently until the file changes really. assertThatThrownBy(() -> f.get(1, TimeUnit.SECONDS)).isInstanceOf(TimeoutException.class); // Remove the file being watched again. repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, Change.ofRemoval(path)).join(); // Should wait patiently until the file reappears. assertThatThrownBy(() -> f.get(1, TimeUnit.SECONDS)).isInstanceOf(TimeoutException.class); // Add the file back again with different content. final Revision rev2 = repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, upsert2).join(); // Should be notified this time because the content has changed really. final Entry<JsonNode> res = f.get(3, TimeUnit.SECONDS); assertThat(res.revision()).isEqualTo(rev2); assertThat(res.type()).isEqualTo(EntryType.JSON); assertThatJson(res.content()).isEqualTo(upsert2.content()); }
@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 testDiff_remove() throws Exception { final CentralDogma client = rule.client(); final Revision rev1 = client.push(rule.project(), rule.repo1(), HEAD, "summary1", Change.ofTextUpsert("/foo.txt", "hello")).join().revision(); final Revision rev2 = client.push(rule.project(), rule.repo1(), HEAD, "summary2", Change.ofRemoval("/foo.txt")).join().revision(); assertThat(rev1.forward(1)).isEqualTo(rev2); assertThat(client.getDiff(rule.project(), rule.repo1(), rev1, rev1, Query.ofText("/foo.txt")).join().type()) .isEqualTo(ChangeType.APPLY_TEXT_PATCH); assertThat(client.getDiff(rule.project(), rule.repo1(), rev1, rev2, Query.ofText("/foo.txt")).join()) .isEqualTo(Change.ofRemoval("/foo.txt")); }
@Test public void diffFile() throws Exception { doAnswer(invocation -> { final AsyncMethodCallback<DiffFileResult> callback = invocation.getArgument(5); callback.onComplete(new DiffFileResult(ChangeType.UPSERT_TEXT, "some_text")); return null; }).when(iface).diffFile(any(), any(), any(), any(), any(), any()); assertThat(client.getDiff("project", "repo", new Revision(1), new Revision(3), Query.ofText("/a.txt")).get()) .isEqualTo(Change.ofTextUpsert("/a.txt", "some_text")); verify(iface).diffFile(eq("project"), eq("repo"), any(), any(), any(), any()); }
@Test public void testPreviewDiff() { final Map<String, Change<?>> changeMap = repo.previewDiff(HEAD, jsonUpserts[0]).join(); assertThat(changeMap).containsEntry(jsonPaths[0], jsonUpserts[0]); // Invalid patch assertThatThrownBy(() -> repo.previewDiff(HEAD, jsonPatches[1]).join()) .isInstanceOf(CompletionException.class) .hasCauseInstanceOf(ChangeConflictException.class); // Invalid removal assertThatThrownBy(() -> repo.previewDiff(HEAD, Change.ofRemoval(jsonPaths[0])).join()) .isInstanceOf(CompletionException.class) .hasCauseInstanceOf(ChangeConflictException.class); // Apply a series of changes final List<Change<?>> changes = Arrays.asList(jsonUpserts[0], jsonPatches[1], jsonPatches[2], Change.ofRename(jsonPaths[0], jsonPaths[1]), Change.ofRemoval(jsonPaths[1])); Map<String, Change<?>> returnedChangeMap = repo.previewDiff(HEAD, changes).join(); assertThat(returnedChangeMap).isEmpty(); assertThatThrownBy(() -> repo.previewDiff(new Revision(Integer.MAX_VALUE), changes).join()) .isInstanceOf(CompletionException.class) .hasCauseInstanceOf(RevisionNotFoundException.class); assertThat(repo.previewDiff(new Revision(-1), Collections.emptyList()).join()).isEmpty(); // Test upsert on an existing path repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, jsonPatches[0], jsonPatches[1]).join(); returnedChangeMap = repo.previewDiff(HEAD, jsonUpserts[0]).join(); assertThat(returnedChangeMap.get(jsonPaths[0]).type()).isEqualTo(ChangeType.APPLY_JSON_PATCH); }
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(); }
@Test public void testRemoval() throws Exception { // A removal should fail when there's no such entry. assertThatThrownBy(() -> repo .commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, Change.ofRemoval(jsonPaths[0])).join()) .isInstanceOf(CompletionException.class) .hasCauseInstanceOf(ChangeConflictException.class); Revision revision = repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, jsonUpserts[0]).join(); assertThat(repo.exists(revision, jsonPaths[0]).join()).isTrue(); // A removal should succeed when there's an entry. revision = repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, Change.ofRemoval(jsonPaths[0])).join(); assertThat(repo.exists(revision, jsonPaths[0]).join()).isFalse(); // A removal should fail when there's no such entry. assertThatThrownBy(() -> repo .commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, Change.ofRemoval(jsonPaths[0])).join()) .isInstanceOf(CompletionException.class) .hasCauseInstanceOf(ChangeConflictException.class); }
@Test public void testQueryByRange() throws Exception { final String path = "/test_json_file.json"; for (int i = 0; i < 5; i++) { final Change<JsonNode> change = Change.ofJsonUpsert(path, String.format("{ \"key\" : \"%d\"}", i)); rule.client().push( rule.project(), rule.repo1(), HEAD, TestConstants.randomText(), change).join(); } final Change<JsonNode> res = rule.client().getDiff( rule.project(), rule.repo1(), new Revision(-4), new Revision(-1), Query.ofJsonPath(path, "$.key")).join(); assertThat(res.type()).isEqualTo(ChangeType.APPLY_JSON_PATCH); assertThatJson(res.content()).isEqualTo( "[{" + " \"op\": \"safeReplace\"," + " \"path\": \"\"," + " \"oldValue\": \"1\"," + " \"value\": \"4\"" + "}]"); }
static ChangeDto convert(Change<?> change) { final ChangeDto dto = new ChangeDto(); dto.setPath(change.path()); dto.setType(change.type().name()); dto.setContent(change.contentAsText()); return dto; }
@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); }
@Test public void testMultipleChanges() throws Exception { final List<Change<?>> changes = new ArrayList<>(); Collections.addAll(changes, jsonUpserts); for (int i = 1; i < jsonPatches.length; i++) { changes.add(jsonPatches[i]); } repo.commit(HEAD, 0L, Author.UNKNOWN, SUMMARY, changes).join(); final Map<String, Entry<?>> entries = repo.find(HEAD, allPattern).join(); assertThat(entries).hasSize(jsonUpserts.length); for (int i = 0; i < jsonUpserts.length; i++) { final Change<?> c = jsonUpserts[i]; assertThat(entries).containsKey(c.path()); if (i == 0) { // We have patched the first upsert to make it identical to the last upsert. assertThatJson(entries.get(c.path()).content()) .isEqualTo(jsonUpserts[jsonUpserts.length - 1].content()); } else { assertThatJson(entries.get(c.path()).content()).isEqualTo(c.content()); } } }
final String newTextString = String.format(textStringPattern, i); final Change<JsonNode> jsonChange = Change.ofJsonPatch(jsonNodePath, oldJsonString, newJsonString); final Change<String> textChange = Change.ofTextPatch(textNodePath, oldTextString, newTextString);