diff --git a/spring-batch-notion/pom.xml b/spring-batch-notion/pom.xml
index a896abdb..3546275e 100644
--- a/spring-batch-notion/pom.xml
+++ b/spring-batch-notion/pom.xml
@@ -61,12 +61,16 @@
org.springframework
- spring-beans
+ spring-web
org.springframework.batch
spring-batch-infrastructure
+
+ tools.jackson.core
+ jackson-databind
+
com.h2database
diff --git a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/NotionDatabaseItemReader.java b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/NotionDatabaseItemReader.java
index 8c1455d8..04e0df37 100644
--- a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/NotionDatabaseItemReader.java
+++ b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/NotionDatabaseItemReader.java
@@ -15,22 +15,19 @@
*/
package org.springframework.batch.extensions.notion;
-import notion.api.v1.NotionClient;
-import notion.api.v1.http.JavaNetHttpClient;
-import notion.api.v1.logging.Slf4jLogger;
-import notion.api.v1.model.databases.QueryResults;
-import notion.api.v1.model.databases.query.filter.QueryTopLevelFilter;
-import notion.api.v1.model.databases.query.sort.QuerySort;
-import notion.api.v1.model.pages.Page;
-import notion.api.v1.model.pages.PageProperty;
-import notion.api.v1.model.pages.PageProperty.RichText;
-import notion.api.v1.request.databases.QueryDatabaseRequest;
import org.jspecify.annotations.Nullable;
+import org.springframework.batch.extensions.notion.PageProperty.RichTextProperty;
+import org.springframework.batch.extensions.notion.PageProperty.TitleProperty;
import org.springframework.batch.extensions.notion.mapping.PropertyMapper;
import org.springframework.batch.infrastructure.item.ExecutionContext;
import org.springframework.batch.infrastructure.item.ItemReader;
import org.springframework.batch.infrastructure.item.data.AbstractPaginatedDataItemReader;
+import org.springframework.http.HttpHeaders;
import org.springframework.util.Assert;
+import org.springframework.web.client.ApiVersionInserter;
+import org.springframework.web.client.RestClient;
+import org.springframework.web.client.support.RestClientAdapter;
+import org.springframework.web.service.invoker.HttpServiceProxyFactory;
import java.util.Collections;
import java.util.Iterator;
@@ -39,7 +36,6 @@
import java.util.Map.Entry;
import java.util.Objects;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
/**
* Restartable {@link ItemReader} that reads entries from a Notion database via a paging
@@ -71,11 +67,11 @@ public class NotionDatabaseItemReader extends AbstractPaginatedDataItemReader
private String baseUrl = DEFAULT_BASE_URL;
- private @Nullable QueryTopLevelFilter filter;
+ private @Nullable Filter filter;
- private @Nullable List sorts;
+ private Sort[] sorts = new Sort[0];
- private @Nullable NotionClient client;
+ private @Nullable NotionDatabaseService service;
private boolean hasMore;
@@ -117,7 +113,7 @@ public void setBaseUrl(String baseUrl) {
* @see Filter#where(Filter)
*/
public void setFilter(Filter filter) {
- this.filter = filter.toQueryTopLevelFilter();
+ this.filter = filter;
}
/**
@@ -130,7 +126,7 @@ public void setFilter(Filter filter) {
* @see Sort#by(Sort.Timestamp)
*/
public void setSorts(Sort... sorts) {
- this.sorts = Stream.of(sorts).map(Sort::toQuerySort).toList();
+ this.sorts = sorts;
}
/**
@@ -151,10 +147,15 @@ public void setPageSize(int pageSize) {
*/
@Override
protected void doOpen() {
- client = new NotionClient(token);
- client.setHttpClient(new JavaNetHttpClient());
- client.setLogger(new Slf4jLogger());
- client.setBaseUrl(baseUrl);
+ RestClient restClient = RestClient.builder()
+ .baseUrl(baseUrl)
+ .apiVersionInserter(ApiVersionInserter.useHeader("Notion-Version"))
+ .defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token)
+ .build();
+
+ RestClientAdapter adapter = RestClientAdapter.create(restClient);
+ HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
+ service = factory.createClient(NotionDatabaseService.class);
hasMore = true;
}
@@ -168,53 +169,47 @@ protected Iterator doPageRead() {
return Collections.emptyIterator();
}
- QueryDatabaseRequest request = new QueryDatabaseRequest(databaseId);
- request.setFilter(filter);
- request.setSorts(sorts);
- request.setStartCursor(nextCursor);
- request.setPageSize(pageSize);
+ QueryRequest request = new QueryRequest(pageSize, nextCursor, filter, sorts);
@SuppressWarnings("DataFlowIssue")
- QueryResults queryResults = client.queryDatabase(request);
+ QueryResult result = service.query(databaseId, request);
- hasMore = queryResults.getHasMore();
- nextCursor = queryResults.getNextCursor();
+ hasMore = result.hasMore();
+ nextCursor = result.nextCursor();
- return queryResults.getResults()
+ return result.results()
.stream()
.map(NotionDatabaseItemReader::getProperties)
.map(propertyMapper::map)
.iterator();
}
- private static Map getProperties(Page element) {
- return element.getProperties()
+ private static Map getProperties(Page page) {
+ return page.properties()
.entrySet()
.stream()
.collect(Collectors.toUnmodifiableMap(Entry::getKey, entry -> getPropertyValue(entry.getValue())));
}
private static String getPropertyValue(PageProperty property) {
- return switch (property.getType()) {
- case RichText -> getPlainText(property.getRichText());
- case Title -> getPlainText(property.getTitle());
- default -> throw new IllegalArgumentException("Unsupported type: " + property.getType());
- };
+ if (property instanceof RichTextProperty p) {
+ return getPlainText(p.richText());
+ }
+ if (property instanceof TitleProperty p) {
+ return getPlainText(p.title());
+ }
+ throw new IllegalArgumentException("Unsupported type: " + property.getClass());
}
private static String getPlainText(List texts) {
- return texts.isEmpty() ? "" : texts.get(0).getPlainText();
+ return texts.isEmpty() ? "" : texts.get(0).plainText();
}
/**
* {@inheritDoc}
*/
- @SuppressWarnings("DataFlowIssue")
@Override
protected void doClose() {
- client.close();
- client = null;
-
hasMore = false;
}
diff --git a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/NotionDatabaseService.java b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/NotionDatabaseService.java
new file mode 100644
index 00000000..1e0e88d8
--- /dev/null
+++ b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/NotionDatabaseService.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.batch.extensions.notion;
+
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.service.annotation.HttpExchange;
+import org.springframework.web.service.annotation.PostExchange;
+
+@HttpExchange(url = "/databases", version = "2022-06-28", accept = MediaType.APPLICATION_JSON_VALUE)
+interface NotionDatabaseService {
+
+ @PostExchange("/{databaseId}/query")
+ QueryResult query(@PathVariable String databaseId, @RequestBody QueryRequest request);
+
+}
diff --git a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/Page.java b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/Page.java
new file mode 100644
index 00000000..b800b692
--- /dev/null
+++ b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/Page.java
@@ -0,0 +1,6 @@
+package org.springframework.batch.extensions.notion;
+
+import java.util.Map;
+
+record Page(Map properties) {
+}
diff --git a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/PageProperty.java b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/PageProperty.java
new file mode 100644
index 00000000..ee4f5a69
--- /dev/null
+++ b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/PageProperty.java
@@ -0,0 +1,23 @@
+package org.springframework.batch.extensions.notion;
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+import tools.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
+import tools.jackson.databind.annotation.JsonNaming;
+
+import java.util.List;
+
+@JsonTypeInfo(use = Id.NAME, property = "type")
+interface PageProperty {
+
+ @JsonTypeName("rich_text")
+ @JsonNaming(SnakeCaseStrategy.class)
+ record RichTextProperty(List richText) implements PageProperty {
+ }
+
+ @JsonTypeName("title")
+ record TitleProperty(List title) implements PageProperty {
+ }
+
+}
diff --git a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/QueryRequest.java b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/QueryRequest.java
new file mode 100644
index 00000000..bafd7c67
--- /dev/null
+++ b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/QueryRequest.java
@@ -0,0 +1,14 @@
+package org.springframework.batch.extensions.notion;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import org.jspecify.annotations.Nullable;
+import tools.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
+import tools.jackson.databind.annotation.JsonNaming;
+
+import java.util.List;
+
+@JsonNaming(SnakeCaseStrategy.class)
+@JsonInclude(Include.NON_EMPTY)
+record QueryRequest(int pageSize, @Nullable String startCursor, @Nullable Filter filter, Sort... sorts) {
+}
diff --git a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/QueryResult.java b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/QueryResult.java
new file mode 100644
index 00000000..a91a4206
--- /dev/null
+++ b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/QueryResult.java
@@ -0,0 +1,11 @@
+package org.springframework.batch.extensions.notion;
+
+import tools.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
+import tools.jackson.databind.annotation.JsonNaming;
+
+import java.util.List;
+
+@JsonNaming(SnakeCaseStrategy.class)
+record QueryResult(List results, String nextCursor, boolean hasMore) {
+
+}
diff --git a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/RichText.java b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/RichText.java
new file mode 100644
index 00000000..983534bc
--- /dev/null
+++ b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/RichText.java
@@ -0,0 +1,8 @@
+package org.springframework.batch.extensions.notion;
+
+import tools.jackson.databind.PropertyNamingStrategies;
+import tools.jackson.databind.annotation.JsonNaming;
+
+@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+record RichText(String plainText) {
+}
diff --git a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/Sort.java b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/Sort.java
index d42a51bb..e285e0a9 100644
--- a/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/Sort.java
+++ b/spring-batch-notion/src/main/java/org/springframework/batch/extensions/notion/Sort.java
@@ -15,9 +15,10 @@
*/
package org.springframework.batch.extensions.notion;
-import notion.api.v1.model.databases.query.sort.QuerySort;
-import notion.api.v1.model.databases.query.sort.QuerySortDirection;
-import notion.api.v1.model.databases.query.sort.QuerySortTimestamp;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import tools.jackson.databind.EnumNamingStrategies;
+import tools.jackson.databind.EnumNamingStrategies.SnakeCaseStrategy;
+import tools.jackson.databind.annotation.EnumNaming;
import java.util.Objects;
@@ -81,66 +82,48 @@ public static Sort by(Timestamp timestamp) {
/**
* Timestamps associated with database entries.
*/
+ @EnumNaming(SnakeCaseStrategy.class)
public enum Timestamp {
/**
* The time the entry was created.
*/
- CREATED_TIME(QuerySortTimestamp.CreatedTime),
+ CREATED_TIME,
/**
* The time the entry was last edited.
*/
- LAST_EDITED_TIME(QuerySortTimestamp.LastEditedTime);
-
- private final QuerySortTimestamp querySortTimestamp;
-
- Timestamp(QuerySortTimestamp querySortTimestamp) {
- this.querySortTimestamp = querySortTimestamp;
- }
-
- private QuerySortTimestamp getQuerySortTimestamp() {
- return querySortTimestamp;
- }
+ LAST_EDITED_TIME;
}
/**
* Sort directions.
*/
+ @EnumNaming(SnakeCaseStrategy.class)
public enum Direction {
/**
* Ascending direction.
*/
- ASCENDING(QuerySortDirection.Ascending),
+ ASCENDING,
/**
* Descending direction.
*/
- DESCENDING(QuerySortDirection.Descending);
-
- private final QuerySortDirection querySortDirection;
-
- Direction(QuerySortDirection querySortDirection) {
- this.querySortDirection = querySortDirection;
- }
-
- private QuerySortDirection getQuerySortDirection() {
- return querySortDirection;
- }
+ DESCENDING;
}
private Sort() {
}
- abstract QuerySort toQuerySort();
-
private static final class PropertySort extends Sort {
+ @JsonProperty
private final String property;
+ @JsonProperty
private final Direction direction;
private PropertySort(String property, Direction direction) {
@@ -148,11 +131,6 @@ private PropertySort(String property, Direction direction) {
this.direction = Objects.requireNonNull(direction);
}
- @Override
- QuerySort toQuerySort() {
- return new QuerySort(property, null, direction.getQuerySortDirection());
- }
-
@Override
public String toString() {
return "%s: %s".formatted(property, direction);
@@ -162,8 +140,10 @@ public String toString() {
private static final class TimestampSort extends Sort {
+ @JsonProperty
private final Timestamp timestamp;
+ @JsonProperty
private final Direction direction;
private TimestampSort(Timestamp timestamp, Direction direction) {
@@ -171,11 +151,6 @@ private TimestampSort(Timestamp timestamp, Direction direction) {
this.direction = Objects.requireNonNull(direction);
}
- @Override
- QuerySort toQuerySort() {
- return new QuerySort(null, timestamp.getQuerySortTimestamp(), direction.getQuerySortDirection());
- }
-
@Override
public String toString() {
return "%s: %s".formatted(timestamp, direction);
diff --git a/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/FilterTests.java b/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/FilterTests.java
index 0b8e88c4..9917192c 100644
--- a/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/FilterTests.java
+++ b/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/FilterTests.java
@@ -27,6 +27,7 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.FieldSource;
+import tools.jackson.databind.json.JsonMapper;
import java.util.List;
import java.util.function.Supplier;
@@ -34,6 +35,7 @@
import static org.assertj.core.api.BDDAssertions.then;
import static org.junit.jupiter.params.provider.Arguments.arguments;
+import static org.skyscreamer.jsonassert.JSONAssert.assertEquals;
import static org.springframework.batch.extensions.notion.Filter.where;
/**
@@ -41,6 +43,17 @@
*/
class FilterTests {
+ private final JsonMapper jsonMapper = new JsonMapper();
+
+ @ParameterizedTest
+ @FieldSource({ "PROPERTY_FILTERS", "COMPOUND_FILTERS", "NESTED_FILTERS" })
+ void toJson(Filter underTest, String expected) throws Exception {
+ // WHEN
+ String result = jsonMapper.writeValueAsString(underTest);
+ // THEN
+ assertEquals(expected, result, true);
+ }
+
@ParameterizedTest
@FieldSource({ "PROPERTY_FILTERS", "COMPOUND_FILTERS", "NESTED_FILTERS" })
void toQueryTopLevelFilter(Filter underTest, QueryTopLevelFilter expected) {
@@ -52,24 +65,26 @@ void toQueryTopLevelFilter(Filter underTest, QueryTopLevelFilter expected) {
static final List CHECKBOX_FILTERS = Stream.of(true, false)
.flatMap(value -> Stream.of( //
- arguments( //
- where().checkbox("property").isEqualTo(value), //
- supply(() -> {
- CheckboxFilter checkboxFilter = new CheckboxFilter();
- checkboxFilter.setEquals(value);
- PropertyFilter propertyFilter = new PropertyFilter("property");
- propertyFilter.setCheckbox(checkboxFilter);
- return propertyFilter;
- })),
- arguments( //
- where().checkbox("property").isNotEqualTo(value), //
- supply(() -> {
- CheckboxFilter checkboxFilter = new CheckboxFilter();
- checkboxFilter.setDoesNotEqual(value);
- PropertyFilter propertyFilter = new PropertyFilter("property");
- propertyFilter.setCheckbox(checkboxFilter);
- return propertyFilter;
- }))))
+ arguments(where().checkbox("property").isEqualTo(value), """
+ {
+ "filter": {
+ "property": "property",
+ "checkbox": {
+ "equals": %s
+ }
+ }
+ }
+ """.formatted(value)), //
+ arguments(where().checkbox("property").isNotEqualTo(value), """
+ {
+ "filter": {
+ "property": "property",
+ "checkbox": {
+ "does_not_equal": %s
+ }
+ }
+ }
+ """.formatted(value))))
.toList();
static final List FILES_FILTERS = List.of( //
diff --git a/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/NotionJvmSdkTests.java b/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/NotionJvmSdkTests.java
deleted file mode 100644
index 780a2f74..00000000
--- a/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/NotionJvmSdkTests.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2024-2025 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.springframework.batch.extensions.notion;
-
-import com.tngtech.archunit.base.DescribedPredicate;
-import com.tngtech.archunit.core.domain.JavaClass;
-import com.tngtech.archunit.core.domain.JavaClasses;
-import com.tngtech.archunit.junit.AnalyzeClasses;
-import com.tngtech.archunit.junit.ArchTest;
-import com.tngtech.archunit.lang.ArchRule;
-
-import static com.tngtech.archunit.base.DescribedPredicate.anyElementThat;
-import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAPackage;
-import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
-
-/**
- * @author Stefano Cordio
- */
-@AnalyzeClasses(packagesOf = NotionDatabaseItemReader.class)
-class NotionJvmSdkTests {
-
- private static final DescribedPredicate RESIDE_IN_NOTION_JVM_SDK_PACKAGE = //
- resideInAPackage("notion.api..");
-
- @ArchTest
- void library_types_should_not_be_exposed(JavaClasses classes) {
- // @formatter:off
- ArchRule rule = methods()
- .that().arePublic().or().areProtected()
- .should().notHaveRawReturnType(RESIDE_IN_NOTION_JVM_SDK_PACKAGE)
- .andShould().notHaveRawParameterTypes(anyElementThat(RESIDE_IN_NOTION_JVM_SDK_PACKAGE));
- // @formatter:on
- rule.check(classes);
- }
-
-}
diff --git a/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/PagePropertyTests.java b/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/PagePropertyTests.java
new file mode 100644
index 00000000..933c7c23
--- /dev/null
+++ b/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/PagePropertyTests.java
@@ -0,0 +1,132 @@
+package org.springframework.batch.extensions.notion;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.batch.extensions.notion.PageProperty.RichTextProperty;
+import org.springframework.batch.extensions.notion.PageProperty.TitleProperty;
+import tools.jackson.core.type.TypeReference;
+import tools.jackson.databind.json.JsonMapper;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.assertj.core.api.BDDAssertions.then;
+
+class PagePropertyTests {
+
+ private final JsonMapper jsonMapper = new JsonMapper();
+
+ @Test
+ void richTextProperty() {
+ // GIVEN
+ String json = """
+ {
+ "Description": {
+ "id": "HbZT",
+ "type": "rich_text",
+ "rich_text": [
+ {
+ "type": "text",
+ "text": {
+ "content": "There is some ",
+ "link": null
+ },
+ "annotations": {
+ "bold": false,
+ "italic": false,
+ "strikethrough": false,
+ "underline": false,
+ "code": false,
+ "color": "default"
+ },
+ "plain_text": "There is some ",
+ "href": null
+ },
+ {
+ "type": "text",
+ "text": {
+ "content": "text",
+ "link": null
+ },
+ "annotations": {
+ "bold": true,
+ "italic": false,
+ "strikethrough": false,
+ "underline": false,
+ "code": false,
+ "color": "default"
+ },
+ "plain_text": "text",
+ "href": null
+ },
+ {
+ "type": "text",
+ "text": {
+ "content": " in this property!",
+ "link": null
+ },
+ "annotations": {
+ "bold": false,
+ "italic": false,
+ "strikethrough": false,
+ "underline": false,
+ "code": false,
+ "color": "default"
+ },
+ "plain_text": " in this property!",
+ "href": null
+ }
+ ]
+ }
+ }
+ """;
+ Map expected = Map.of("Description", new RichTextProperty(List.of( //
+ new RichText("There is some "), //
+ new RichText("text"), //
+ new RichText(" in this property!"))));
+ // WHEN
+ Map result = jsonMapper.readValue(json, new TypeReference<>() {
+ });
+ // THEN
+ then(result).isEqualTo(expected);
+ }
+
+ @Test
+ void titleProperty() {
+ // GIVEN
+ String json = """
+ {
+ "Title": {
+ "id": "title",
+ "type": "title",
+ "title": [
+ {
+ "type": "text",
+ "text": {
+ "content": "A better title for the page",
+ "link": null
+ },
+ "annotations": {
+ "bold": false,
+ "italic": false,
+ "strikethrough": false,
+ "underline": false,
+ "code": false,
+ "color": "default"
+ },
+ "plain_text": "This is also not done",
+ "href": null
+ }
+ ]
+ }
+ }
+ """;
+ Map expected = Map.of("Title",
+ new TitleProperty(List.of(new RichText("This is also not done"))));
+ // WHEN
+ Map result = jsonMapper.readValue(json, new TypeReference<>() {
+ });
+ // THEN
+ then(result).isEqualTo(expected);
+ }
+
+}
diff --git a/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/QueryRequestTests.java b/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/QueryRequestTests.java
new file mode 100644
index 00000000..68fc68c1
--- /dev/null
+++ b/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/QueryRequestTests.java
@@ -0,0 +1,65 @@
+package org.springframework.batch.extensions.notion;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.FieldSource;
+import tools.jackson.databind.json.JsonMapper;
+
+import java.util.List;
+
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+import static org.skyscreamer.jsonassert.JSONAssert.assertEquals;
+import static org.springframework.batch.extensions.notion.Sort.Timestamp.CREATED_TIME;
+
+class QueryRequestTests {
+
+ private final JsonMapper jsonMapper = new JsonMapper();
+
+ @ParameterizedTest
+ @FieldSource
+ void toJson(QueryRequest underTest, String expected) throws Exception {
+ // WHEN
+ String result = jsonMapper.writeValueAsString(underTest);
+ // THEN
+ assertEquals(expected, result, true);
+ }
+
+ static List> toJson = List.of( //
+ arguments(new QueryRequest(42, null, null), """
+ {
+ "page_size" : 42
+ }
+ """), //
+ arguments(new QueryRequest(42, "cursor", null), """
+ {
+ "page_size" : 42,
+ "start_cursor" : "cursor"
+ }
+ """), //
+ arguments(new QueryRequest(42, null, null, Sort.by("property")), """
+ {
+ "page_size" : 42,
+ "sorts" : [
+ {
+ "direction" : "ascending",
+ "property" : "property"
+ }
+ ]
+ }
+ """), //
+ arguments(new QueryRequest(42, null, null, Sort.by("property"), Sort.by(CREATED_TIME)), """
+ {
+ "page_size" : 42,
+ "sorts" : [
+ {
+ "property" : "property",
+ "direction" : "ascending"
+ },
+ {
+ "timestamp" : "created_time",
+ "direction" : "ascending"
+ }
+ ]
+ }
+ """));
+
+}
diff --git a/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/RichTextTests.java b/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/RichTextTests.java
new file mode 100644
index 00000000..4b0518fa
--- /dev/null
+++ b/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/RichTextTests.java
@@ -0,0 +1,41 @@
+package org.springframework.batch.extensions.notion;
+
+import org.junit.jupiter.api.Test;
+import tools.jackson.databind.json.JsonMapper;
+
+import static org.assertj.core.api.BDDAssertions.then;
+
+class RichTextTests {
+
+ private final JsonMapper jsonMapper = new JsonMapper();
+
+ @Test
+ void fromJson() {
+ // GIVEN
+ String json = """
+ {
+ "type": "text",
+ "text": {
+ "content": "Some words ",
+ "link": null
+ },
+ "annotations": {
+ "bold": false,
+ "italic": false,
+ "strikethrough": false,
+ "underline": false,
+ "code": false,
+ "color": "default"
+ },
+ "plain_text": "Some words ",
+ "href": null
+ }
+ """;
+ RichText expected = new RichText("Some words ");
+ // WHEN
+ RichText result = jsonMapper.readValue(json, RichText.class);
+ // THEN
+ then(result).isEqualTo(expected);
+ }
+
+}
diff --git a/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/SortTests.java b/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/SortTests.java
index fbd387b7..7b878871 100644
--- a/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/SortTests.java
+++ b/spring-batch-notion/src/test/java/org/springframework/batch/extensions/notion/SortTests.java
@@ -15,22 +15,16 @@
*/
package org.springframework.batch.extensions.notion;
-import notion.api.v1.model.databases.query.sort.QuerySort;
-import notion.api.v1.model.databases.query.sort.QuerySortDirection;
-import notion.api.v1.model.databases.query.sort.QuerySortTimestamp;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.FieldSource;
+import tools.jackson.databind.json.JsonMapper;
import java.util.List;
-import static notion.api.v1.model.databases.query.sort.QuerySortDirection.Ascending;
-import static notion.api.v1.model.databases.query.sort.QuerySortDirection.Descending;
-import static notion.api.v1.model.databases.query.sort.QuerySortTimestamp.CreatedTime;
-import static notion.api.v1.model.databases.query.sort.QuerySortTimestamp.LastEditedTime;
-import static org.assertj.core.api.BDDAssertions.from;
import static org.assertj.core.api.BDDAssertions.then;
import static org.junit.jupiter.params.provider.Arguments.arguments;
+import static org.skyscreamer.jsonassert.JSONAssert.assertEquals;
import static org.springframework.batch.extensions.notion.Sort.Direction.ASCENDING;
import static org.springframework.batch.extensions.notion.Sort.Direction.DESCENDING;
import static org.springframework.batch.extensions.notion.Sort.Timestamp.CREATED_TIME;
@@ -41,28 +35,72 @@
*/
class SortTests {
+ private final JsonMapper jsonMapper = new JsonMapper();
+
@ParameterizedTest
@FieldSource
- void toQuerySort(Sort underTest, String property, QuerySortTimestamp timestamp, QuerySortDirection direction) {
+ void toJson(Sort underTest, String expected) throws Exception {
// WHEN
- QuerySort result = underTest.toQuerySort();
+ String result = jsonMapper.writeValueAsString(underTest);
// THEN
- then(result) //
- .returns(direction, from(QuerySort::getDirection))
- .returns(property, from(QuerySort::getProperty))
- .returns(timestamp, from(QuerySort::getTimestamp));
+ assertEquals(expected, result, true);
}
- static List toQuerySort = List.of( //
- arguments(Sort.by("property"), "property", null, Ascending),
- arguments(Sort.by("property", ASCENDING), "property", null, Ascending),
- arguments(Sort.by("property", DESCENDING), "property", null, Descending),
- arguments(Sort.by(CREATED_TIME), null, CreatedTime, Ascending),
- arguments(Sort.by(CREATED_TIME, ASCENDING), null, CreatedTime, Ascending),
- arguments(Sort.by(CREATED_TIME, DESCENDING), null, CreatedTime, Descending),
- arguments(Sort.by(LAST_EDITED_TIME), null, LastEditedTime, Ascending),
- arguments(Sort.by(LAST_EDITED_TIME, ASCENDING), null, LastEditedTime, Ascending),
- arguments(Sort.by(LAST_EDITED_TIME, DESCENDING), null, LastEditedTime, Descending));
+ static List> toJson = List.of( //
+ arguments(Sort.by("property"), """
+ {
+ "property" : "property",
+ "direction" : "ascending"
+ }
+ """), //
+ arguments(Sort.by("property", ASCENDING), """
+ {
+ "property" : "property",
+ "direction" : "ascending"
+ }
+ """), //
+ arguments(Sort.by("property", DESCENDING), """
+ {
+ "property" : "property",
+ "direction" : "descending"
+ }
+ """), //
+ arguments(Sort.by(CREATED_TIME), """
+ {
+ "timestamp" : "created_time",
+ "direction" : "ascending"
+ }
+ """), //
+ arguments(Sort.by(CREATED_TIME, ASCENDING), """
+ {
+ "timestamp" : "created_time",
+ "direction" : "ascending"
+ }
+ """), //
+ arguments(Sort.by(CREATED_TIME, DESCENDING), """
+ {
+ "timestamp" : "created_time",
+ "direction" : "descending"
+ }
+ """), //
+ arguments(Sort.by(LAST_EDITED_TIME, DESCENDING), """
+ {
+ "timestamp" : "last_edited_time",
+ "direction" : "descending"
+ }
+ """), //
+ arguments(Sort.by(LAST_EDITED_TIME, DESCENDING), """
+ {
+ "timestamp" : "last_edited_time",
+ "direction" : "descending"
+ }
+ """), //
+ arguments(Sort.by(LAST_EDITED_TIME, DESCENDING), """
+ {
+ "timestamp" : "last_edited_time",
+ "direction" : "descending"
+ }
+ """));
@ParameterizedTest
@FieldSource