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}