001package org.apache.turbine.services.localization;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import static org.junit.jupiter.api.Assertions.assertEquals;
023import static org.junit.jupiter.api.Assertions.assertNotNull;
024import static org.junit.jupiter.api.DynamicContainer.dynamicContainer;
025import static org.junit.jupiter.api.DynamicTest.dynamicTest;
026import static org.mockito.Mockito.mock;
027
028import java.time.Instant;
029import java.time.LocalDateTime;
030import java.time.ZoneId;
031import java.time.ZonedDateTime;
032import java.time.format.DateTimeFormatter;
033import java.time.temporal.ChronoField;
034import java.time.temporal.ChronoUnit;
035import java.time.temporal.TemporalAccessor;
036import java.util.stream.Stream;
037
038import javax.servlet.ServletConfig;
039import javax.servlet.http.HttpServletRequest;
040import javax.servlet.http.HttpServletResponse;
041
042import org.apache.fulcrum.parser.DefaultParameterParser;
043import org.apache.turbine.annotation.AnnotationProcessor;
044import org.apache.turbine.annotation.TurbineService;
045import org.apache.turbine.services.pull.PullService;
046import org.apache.turbine.services.pull.util.DateTimeFormatterTool;
047import org.apache.turbine.services.rundata.RunDataService;
048import org.apache.turbine.services.velocity.VelocityService;
049import org.apache.turbine.test.BaseTestCase;
050import org.apache.turbine.util.RunData;
051import org.apache.turbine.util.TurbineConfig;
052import org.apache.velocity.context.Context;
053import org.junit.jupiter.api.AfterAll;
054import org.junit.jupiter.api.Assertions;
055import org.junit.jupiter.api.BeforeAll;
056import org.junit.jupiter.api.DynamicNode;
057import org.junit.jupiter.api.MethodOrderer;
058import org.junit.jupiter.api.Order;
059import org.junit.jupiter.api.Test;
060import org.junit.jupiter.api.TestFactory;
061import org.junit.jupiter.api.TestInstance;
062import org.junit.jupiter.api.TestMethodOrder;
063
064/**
065 * Test class for DateTimeFormatter.
066 *
067 */
068@TestInstance(TestInstance.Lifecycle.PER_CLASS)
069@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
070public class DateTimeFormatterServiceTest extends BaseTestCase {
071
072    @TurbineService
073    private DateTimeFormatterService df;
074
075    private TurbineConfig tc = null;
076
077    @TurbineService
078    private PullService pullService;
079
080    DateTimeFormatterTool dateTimeFormatterTool;
081
082    @TurbineService
083    private VelocityService vs = null;
084
085    @TurbineService
086    RunDataService runDataService = null;
087
088    @BeforeAll
089    public void setup() throws Exception
090    {
091        // required to initialize defaults
092        tc = new TurbineConfig(
093                        ".",
094                        "/conf/test/CompleteTurbineResources.properties");
095        tc.initialize();
096
097        AnnotationProcessor.process(this);
098
099        assertNotNull(pullService);
100        assertNotNull(vs);
101    }
102
103    private RunData getRunData() throws Exception
104    {
105        ServletConfig config = mock(ServletConfig.class);
106        HttpServletRequest request = getMockRequest();
107        HttpServletResponse response = mock(HttpServletResponse.class);
108        RunData runData = runDataService.getRunData(request, response, config);
109        assertEquals(DefaultParameterParser.class, runData.getParameters()
110                .getClass(), "Verify we are using Fulcrum parameter parser");
111        return runData;
112    }
113
114    @AfterAll
115    public void tearDown()
116    {
117        vs.shutdown();
118        tc.dispose();
119    }
120
121    /*
122     * Class under test for String format(Date, String)
123     */
124    @Order(1)
125    @Test
126    void testTool() throws Exception
127    {
128        RunData rundata = getRunData();
129        Context requestContext = vs.getContext(rundata);
130        assertNotNull(requestContext);
131        pullService.populateContext(requestContext, rundata);
132
133        // taking from request context
134        dateTimeFormatterTool = (DateTimeFormatterTool) requestContext.get("dateTimeFormatter");
135        assertNotNull(dateTimeFormatterTool);
136    }
137
138    @Order(2)
139    @TestFactory
140    Stream<DynamicNode> testDateTimeFormatterInstances() {
141        // Stream of DateTimeFormatterInterface to check
142        Stream<DateTimeFormatterInterface> inputStream = Stream.of(df,dateTimeFormatterTool);
143        // Executes tests based on the current input value.
144        return inputStream.map(dtf -> dynamicContainer(
145                "Test " + dtf + " in factory container:",
146                Stream.of(
147                        dynamicTest("test formatDateString",() -> formatDateString(dtf) ),
148                        dynamicTest("test formatZonedDateString",() -> formatZonedDateString(dtf) ),
149                        dynamicTest("test defaultMapFromInstant",() -> defaultMapFromInstant(dtf) ),
150                        dynamicTest("test defaultMapInstant",() -> defaultMapInstant(dtf) ),
151                        dynamicTest("test mapDateStringNullString", () -> mapDateStringNullString(dtf)),
152                        dynamicTest("test mapDateStringEmptyString",() -> mapDateStringEmptyString(dtf)),
153                        dynamicTest("test formatDateStringNullFormat",() -> formatDateStringNullFormat(dtf)),
154                        dynamicTest("test formatDateStringNullString",() -> formatDateStringNullString(dtf)),
155                        dynamicTest("test formatDateStringEmptyString",() -> formatDateStringEmptyString(dtf))
156                ))
157        );
158        // Or return a stream of dynamic tests instead of Dynamic nodes,
159        // but this requires Function<DateTimeFormatterInterface, String> displayNameGenerator and
160        // e.g. ThrowingConsumer<DateTimeFormatterInterface> testExecutor = dtf
161        // return DynamicTest.stream(inputStream, displayNameGenerator, testExecutor);
162    }
163
164    void formatDateString(DateTimeFormatterInterface dateTime)
165    {
166        LocalDateTime ldt = LocalDateTime.now();
167        int day = ldt.get(ChronoField.DAY_OF_MONTH);
168        int month = ldt.get(ChronoField.MONTH_OF_YEAR); // one based
169        int year = ldt.get(ChronoField.YEAR);
170
171        String dayString = (day < 10 ? "0" : "") + day;
172        String monthString = (month < 10 ? "0" : "") + month;
173        String ddmmyyyy = dayString + "/" + monthString + "/" + year;
174
175        String mmddyyyy = "" + monthString + "/" + dayString + "/" + year;
176
177        assertEquals(ddmmyyyy,  dateTime.format(ldt, "dd/MM/yyyy"));
178        assertEquals(mmddyyyy, dateTime.format(ldt, "MM/dd/yyyy"));
179    }
180
181    void formatZonedDateString(DateTimeFormatterInterface dateTime)
182    {
183        ZonedDateTime zdt = ZonedDateTime.now();
184        int day = zdt.get(ChronoField.DAY_OF_MONTH);
185        int month = zdt.get(ChronoField.MONTH_OF_YEAR); // one based
186        int year = zdt.get(ChronoField.YEAR);
187        zdt = zdt.truncatedTo(ChronoUnit.MINUTES);
188
189        String dayString = (day < 10 ? "0" : "") + day;
190        String monthString = (month < 10 ? "0" : "") + month;
191        String ddmmyyyy = dayString + "/" + monthString + "/" + year;
192        Assertions.assertEquals(ddmmyyyy, df.format(zdt, "dd/MM/yyyy"));
193
194        int hours = zdt.get(ChronoField.HOUR_OF_DAY);
195        int mins = zdt.get(ChronoField.MINUTE_OF_HOUR);
196        int secs = zdt.get(ChronoField.SECOND_OF_MINUTE);
197        String hourString = (hours < 10 ? "0" : "") + hours;
198        String minsString = (mins < 10 ? "0" : "") + mins;
199        String secsString = (secs < 10 ? "0" : "") + secs;
200
201        String zone = zdt.getZone().getId();
202        /* String offset = */ zdt.getOffset().getId();
203        // offset formatting not easy matchable, removed
204        String mmddyyyy =
205                "" + monthString + "/" + dayString + "/" + year + " " + hourString + ":" + minsString + ":" + secsString + " " + zone;
206        // zone + offset format, removed offset ZZZ
207        assertEquals(mmddyyyy, dateTime.format(zdt, "MM/dd/yyyy HH:mm:ss VV"));
208    }
209
210    void defaultMapFromInstant(DateTimeFormatterInterface dateTime)
211    {
212        DateTimeFormatter incomingFormat = DateTimeFormatter.ISO_DATE_TIME.withZone(ZoneId.systemDefault());
213        // may throws an DateTimeParseException
214        Instant now = Instant.now().truncatedTo(ChronoUnit.MINUTES);
215        String source = incomingFormat.format(now);
216
217        TemporalAccessor dateTimeFromInstant = incomingFormat.parse(source);
218        int day = dateTimeFromInstant.get(ChronoField.DAY_OF_MONTH);
219        int month = dateTimeFromInstant.get(ChronoField.MONTH_OF_YEAR); // one based
220        int year = dateTimeFromInstant.get(ChronoField.YEAR);
221
222        String dayString = (day < 10 ? "0" : "") + day;
223        String monthString = (month < 10 ? "0" : "") + month;
224        String mmddyyyy = "" + monthString + "/" + dayString + "/" + year;
225        assertEquals(mmddyyyy,  dateTime.mapFrom(source, incomingFormat));
226    }
227
228    void defaultMapInstant(DateTimeFormatterInterface dateTime)
229    {
230        String source = dateTime.format(Instant.now());
231
232        TemporalAccessor dateTimeFromInstant = dateTime.getDefaultFormat().parse(source);
233
234        int day = dateTimeFromInstant.get(ChronoField.DAY_OF_MONTH);
235        int month = dateTimeFromInstant.get(ChronoField.MONTH_OF_YEAR); // one based
236        int year = dateTimeFromInstant.get(ChronoField.YEAR);
237
238        String dayString = (day < 10 ? "0" : "") + day;
239        String monthString = (month < 10 ? "0" : "") + month;
240        String yyyymmdd = year + "-" + monthString + "-" + dayString;
241
242        // caution we are mapping from the DateTimeFormatterTool defaultFormat-pattern without time!
243        // ISO_DATE_TIME will throw an error:
244        // java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: HourOfDay
245        DateTimeFormatter outgoingFormat = DateTimeFormatter.ISO_DATE.withZone(ZoneId.systemDefault());
246        Assertions.assertEquals(yyyymmdd, dateTime.mapTo(source, outgoingFormat));
247
248        outgoingFormat = DateTimeFormatter.ISO_LOCAL_DATE.withZone(ZoneId.systemDefault());
249        Assertions.assertEquals(yyyymmdd, dateTime.mapTo(source, outgoingFormat));
250
251        // ISO_OFFSET_DATE :  Unsupported field: OffsetSeconds
252        // ISO_INSTANT; Unsupported field: InstantSeconds
253        yyyymmdd = year + monthString + dayString;
254        outgoingFormat = DateTimeFormatter.BASIC_ISO_DATE.withZone(ZoneId.systemDefault());
255        assertEquals(yyyymmdd, dateTime.mapTo(source, outgoingFormat));
256    }
257    /*
258     * Class under test for String format(null, String)
259     */
260    void mapDateStringNullString(DateTimeFormatterInterface dateTime)
261    {
262        DateTimeFormatter outgoingFormat = DateTimeFormatter.ISO_INSTANT;
263        Assertions.assertEquals("",
264               dateTime.mapFrom(null, outgoingFormat), "null argument should produce an empty String");
265    }
266
267    /*
268     * Class under test for String format(Date, "")
269     */
270    void mapDateStringEmptyString(DateTimeFormatterInterface dateTime )
271    {
272        Instant today = Instant.now();
273        String todayFormatted = df.format(today);
274        Assertions.assertEquals("",
275                dateTime.mapFrom(todayFormatted, null), "Empty pattern should map to empty String");
276    }
277
278    /*
279     * Class under test for String format(null, String)
280     */
281    void formatDateStringNullString(DateTimeFormatterInterface dateTime )
282    {
283        Assertions.assertEquals("",
284               dateTime.format(null, "MM/dd/xyyyy"), "null argument should produce an empty String");
285    }
286
287    /*
288     * Class under test for String format(Date, "")
289     */
290    void formatDateStringEmptyString(DateTimeFormatterInterface dateTime)
291    {
292        Instant today = Instant.now();
293        Assertions.assertEquals("",
294               dateTime.format(today, ""), "Empty pattern should produce empty String");
295    }
296
297    /*
298     * Class under test for String format(Date, "")
299     */
300
301    void formatDateStringNullFormat(DateTimeFormatterInterface dateTime)
302    {
303        Instant today = Instant.now();
304        Assertions.assertEquals("",
305               dateTime.format(today, null), "null pattern should produce empty String");
306    }
307
308}