Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/main/java/run/halo/mcpserver/tools/ContentPayloads.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ static Map<String, Object> singlePageSchema() {
"title", halo(SinglePage.SinglePageSpec.class, "title"),
"slug", halo(SinglePage.SinglePageSpec.class, "slug"),
"excerpt", halo(Post.PostStatus.class, "excerpt"),
"excerptRaw", halo(Post.Excerpt.class, "raw"),
"autoGenerateExcerpt", halo(Post.Excerpt.class, "autoGenerate"),
"published",
ToolSupport.described(
ToolSupport.booleanSchema(), "Whether the single page is currently published."),
Expand Down Expand Up @@ -293,6 +295,14 @@ static Map<String, Object> singlePage(SinglePage page) {
"title", spec == null ? null : spec.getTitle(),
"slug", spec == null ? null : spec.getSlug(),
"excerpt", status == null ? null : status.getExcerpt(),
"excerptRaw",
spec == null || spec.getExcerpt() == null
? null
: spec.getExcerpt().getRaw(),
"autoGenerateExcerpt",
spec == null
|| spec.getExcerpt() == null
|| Boolean.TRUE.equals(spec.getExcerpt().getAutoGenerate()),
"published", published(page.getMetadata(), SinglePage.PUBLISHED_LABEL),
"publishRequested", spec != null && Boolean.TRUE.equals(spec.getPublish()),
"recycled", spec != null && Boolean.TRUE.equals(spec.getDeleted()),
Expand Down
32 changes: 27 additions & 5 deletions src/main/java/run/halo/mcpserver/tools/SinglePageTools.java
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,9 @@ private BuiltInTool createTool() {
booleanSchema(false),
"Whether to publish the initial snapshot immediately."),
"visible", enumSchema(Post.VisibleEnum.class, Post.VisibleEnum.PUBLIC.name()),
"allowComment", booleanSchema(true)),
"allowComment", booleanSchema(true),
"excerpt", nullableStringSchema(),
"autoGenerateExcerpt", booleanSchema()),
List.of("name", "title", "raw")),
ContentPayloads.singlePageSchema(),
CREATE,
Expand Down Expand Up @@ -320,7 +322,9 @@ private BuiltInTool updateTool() {
+ " updating existing content, reuse the rawType returned by"
+ " the read tool."),
"visible", enumSchema(Post.VisibleEnum.class),
"allowComment", booleanSchema()),
"allowComment", booleanSchema(),
"excerpt", nullableStringSchema(),
"autoGenerateExcerpt", booleanSchema()),
List.of("name")),
ContentPayloads.singlePageSchema(),
UPDATE,
Expand Down Expand Up @@ -400,9 +404,7 @@ private static SinglePage.SinglePageSpec spec(
spec.setAllowComment(optionalBoolean(arguments, "allowComment", true));
spec.setPinned(false);
spec.setPriority(0);
var excerpt = new Post.Excerpt();
excerpt.setAutoGenerate(true);
spec.setExcerpt(excerpt);
spec.setExcerpt(excerpt(arguments, null));
return spec;
}

Expand All @@ -420,6 +422,26 @@ private static void applyMetadata(SinglePage page, Map<String, Object> arguments
if (arguments.containsKey("allowComment")) {
spec.setAllowComment(optionalBoolean(arguments, "allowComment", true));
}
if (arguments.containsKey("excerpt") || arguments.containsKey("autoGenerateExcerpt")) {
spec.setExcerpt(excerpt(arguments, spec.getExcerpt()));
}
}

private static Post.Excerpt excerpt(Map<String, Object> arguments, Post.Excerpt current) {
var excerpt = current == null ? new Post.Excerpt() : current;
if (arguments.containsKey("excerpt")) {
excerpt.setRaw(nullableString(arguments, "excerpt"));
}
final boolean autoGenerate;
if (arguments.containsKey("autoGenerateExcerpt")) {
autoGenerate = optionalBoolean(arguments, "autoGenerateExcerpt", true);
} else if (arguments.containsKey("excerpt")) {
autoGenerate = false;
} else {
autoGenerate = current == null || Boolean.TRUE.equals(current.getAutoGenerate());
}
excerpt.setAutoGenerate(autoGenerate);
return excerpt;
}

private static ContentSnapshots.ContentInput content(Map<String, Object> arguments) {
Expand Down
73 changes: 73 additions & 0 deletions src/test/java/run/halo/mcpserver/tools/SinglePageToolsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.springframework.dao.OptimisticLockingFailureException;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import run.halo.app.core.extension.content.Post;
import run.halo.app.core.extension.content.SinglePage;
import run.halo.app.core.extension.content.Snapshot;
import run.halo.app.extension.ReactiveExtensionClient;
Expand Down Expand Up @@ -76,6 +77,78 @@ void updatesPageMetadataWithoutCreatingSnapshot() {
verify(client, times(0)).create(any(run.halo.app.core.extension.content.Snapshot.class));
}

@Test
@SuppressWarnings("unchecked")
void createsPageWithManualExcerpt() {
var storedPage = new AtomicReference<SinglePage>();
when(authorization.username()).thenReturn(Mono.just("admin"));
when(client.create(any(SinglePage.class))).thenAnswer(invocation -> {
var page = invocation.<SinglePage>getArgument(0);
storedPage.set(page);
return Mono.just(page);
});
when(client.create(any(Snapshot.class))).thenAnswer(invocation -> Mono.just(invocation.getArgument(0)));
when(client.fetch(SinglePage.class, "about")).thenAnswer(invocation -> Mono.just(storedPage.get()));
when(client.update(any(SinglePage.class))).thenAnswer(invocation -> Mono.just(invocation.getArgument(0)));
var tools = new SinglePageTools(client, new ContentSnapshots(client), authorization);

StepVerifier.create(tools.create(Map.of(
"name", "about",
"title", "About",
"raw", "Body",
"excerpt", "Manual summary")))
.assertNext(payload -> {
assertThat(storedPage.get().getSpec().getExcerpt().getRaw()).isEqualTo("Manual summary");
assertThat(storedPage.get().getSpec().getExcerpt().getAutoGenerate())
.isFalse();
var item = (Map<String, Object>) payload.data();
assertThat(item.get("excerptRaw")).isEqualTo("Manual summary");
assertThat(item.get("autoGenerateExcerpt")).isEqualTo(false);
})
.verifyComplete();
}

@Test
@SuppressWarnings("unchecked")
void updatesPageExcerptWithoutCreatingSnapshot() {
var page = page(3L);
when(client.fetch(SinglePage.class, "about")).thenReturn(Mono.just(page));
when(client.update(page)).thenReturn(Mono.just(page));
var tools = new SinglePageTools(client, new ContentSnapshots(client), authorization);

StepVerifier.create(tools.update(Map.of("name", "about", "excerpt", "Short summary")))
.assertNext(payload -> {
assertThat(page.getSpec().getExcerpt().getRaw()).isEqualTo("Short summary");
assertThat(page.getSpec().getExcerpt().getAutoGenerate()).isFalse();
var item = (Map<String, Object>) payload.data();
assertThat(item.get("excerptRaw")).isEqualTo("Short summary");
assertThat(item.get("autoGenerateExcerpt")).isEqualTo(false);
})
.verifyComplete();

verify(client, times(1)).update(any(SinglePage.class));
verify(client, never()).create(any(Snapshot.class));
}

@Test
void enablesAutoGeneratedExcerptWithoutClearingTheManualRaw() {
var page = page(3L);
var excerpt = new Post.Excerpt();
excerpt.setRaw("Kept summary");
excerpt.setAutoGenerate(false);
page.getSpec().setExcerpt(excerpt);
when(client.fetch(SinglePage.class, "about")).thenReturn(Mono.just(page));
when(client.update(page)).thenReturn(Mono.just(page));
var tools = new SinglePageTools(client, new ContentSnapshots(client), authorization);

StepVerifier.create(tools.update(Map.of("name", "about", "autoGenerateExcerpt", true)))
.assertNext(payload -> {
assertThat(page.getSpec().getExcerpt().getRaw()).isEqualTo("Kept summary");
assertThat(page.getSpec().getExcerpt().getAutoGenerate()).isTrue();
})
.verifyComplete();
}

@Test
void removesANewSnapshotWhenTheHeadChangedConcurrently() {
var original = page(3L);
Expand Down
Loading