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