Skip to content

Commit

Permalink
Added timezone handling features
Browse files Browse the repository at this point in the history
  • Loading branch information
jfbenckhuijsen committed Apr 16, 2024
1 parent 94fb7f8 commit 39350fd
Show file tree
Hide file tree
Showing 7 changed files with 314 additions and 41 deletions.
49 changes: 48 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ _testcollection:
Note that in this example:
* Collections always need to be prefixed with an underscore "_". This is to let the library
differentiate between the Map datatype and collections.
* Date/time values need to be defined in ISO 8601 format
* Date/time values need to be defined in ISO 8601 format. You can also specify timezone information in your test data,
for example "2024-03-22T12:13:14.123+02:00".
* In case a document should be skipped for checking (both for existance and the fields), prefix it with an underscore
"_". In this case the document "testcollection/testdoc3" is optional. Firestore allows you to skip definition of all
intermediate documents in a path.
Expand All @@ -121,6 +122,52 @@ The use one of the `assertFirestoreJson()` or `assertFirestoreYaml()` methods (d
reference data) to validate the contents of your database. In case the data does not match, an `AssertionError` will
be thrown in a regular JUnit style.

### Options ###

(New feature since 0.3)

You can now specify options to customize the testing behaviour. Each of the default methods now has a variant which
takes an `Options` object. This `Options`-object will affect the way the library works. Updating the options can be done
in a fluent way. As default, the Options object will use "UTC" as the default timezone.

```java
class Tester {
void test() {
assertFirestoreJson(
firestore,
FirestoreUnit.options()
.withZoneId("UTC"),
my_file
);
}
}
```

To specify the data to take the currently specified timezone, you need to enter the expected value in the JSON (or YAML)
document *without* the timezone information (so without the `Z` or `+02:00`); for example:

```json
{
"_testcollection" : {
"timezoned": {
"testTimezoned": "2024-03-22T12:13:14.123"
}
}
}
```

#### Specify the timezone to use for testing ####

Use the `Options.withZoneId()` method to specify the `ZoneId` to use when validating timestamps. In case your application
stores dates in the database with a specific local timezone (i.e. not as UTC or the system default), then comparing dates
will fail as this will use the `ZoneId.systemDefault()`. You can override the zone id to use using this option.

This setting is convenient when your application code automatically interprets dates using a local timezone. In case that
timezone also features daylight-saving time (DST), the actual timezone offset may differ depending on when the test is
executed. By specifying a timezone for comparison, dates can be specified as if timezones are ignored (e.g. as
`"2024-03-22T12:13:14.123Z"`). The tester will then automatically assume the same timezone as specified in the options.

### Limitations ###

This library has the following limitations:
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<groupId>nl.group9</groupId>
<artifactId>firestore-unit</artifactId>
<version>0.0.2</version>
<version>0.0.3</version>
<packaging>jar</packaging>

<name>firestore-unit</name>
Expand Down
36 changes: 27 additions & 9 deletions src/main/java/nl/group9/firestore/unit/FirestoreTester.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
import org.opentest4j.AssertionFailedError;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
Expand All @@ -32,9 +33,11 @@ class FirestoreTester {
private final JsonNode tree;
private final Executor executor;
private final DateTimeFormatter formatter;
private final FirestoreUnit.Options options;

public FirestoreTester(Firestore firestore, JsonNode tree) {
public FirestoreTester(Firestore firestore, FirestoreUnit.Options options, JsonNode tree) {
this.firestore = firestore;
this.options = options;
this.tree = tree;
this.executor = MoreExecutors.directExecutor();
this.formatter = DateTimeFormatter.ISO_DATE_TIME;
Expand Down Expand Up @@ -135,7 +138,11 @@ private void validateField(JsonNode value, Object docValue, String fieldPath) {
} else if (value.isTextual()) {
if (docValue instanceof Timestamp) {
// Date and time
assertPrimitiveValue(value, docValue, Timestamp.class, this::jsonDateTimeToTimestamp, fieldPath);
assertPrimitiveValue(value,
timestampZoZonedDateTime((Timestamp) docValue),
ZonedDateTime.class,
this::jsonDateTimeToZonedDateTime,
fieldPath);
} else if (docValue instanceof DocumentReference) {
assertDocumentReference(value, docValue, fieldPath);
} else {
Expand Down Expand Up @@ -186,12 +193,23 @@ private void assertMapValue(JsonNode value, Object docValue, String fieldPath) {
validateFields(value, fieldPath, mapDocValue::containsKey, mapDocValue::get);
}

private Timestamp jsonDateTimeToTimestamp(JsonNode value) {
Date date = Date.from(LocalDateTime
.parse(value.asText(), this.formatter)
.atZone(ZoneId.systemDefault())
.toInstant());
return Timestamp.of(date);
private ZonedDateTime jsonDateTimeToZonedDateTime(JsonNode value) {
TemporalAccessor parseResult = formatter.parseBest(value.asText(), ZonedDateTime::from, LocalDateTime::from);
ZonedDateTime dateTime;

if (parseResult instanceof LocalDateTime) {
dateTime = ((LocalDateTime) parseResult).atZone(options.getZoneId());
} else {
dateTime = (ZonedDateTime) parseResult;
dateTime = dateTime.toInstant().atZone(options.getZoneId());
}

return dateTime;
}

private ZonedDateTime timestampZoZonedDateTime(Timestamp timestamp) {
Date date = timestamp.toDate();
return date.toInstant().atZone(options.getZoneId());
}

private void assertType(Object docValue, Class<?> type, String fieldPath) {
Expand Down
Loading

0 comments on commit 39350fd

Please sign in to comment.