|
28 | 28 | import static java.time.ZoneOffset.UTC; |
29 | 29 | import static java.time.temporal.ChronoUnit.DAYS; |
30 | 30 | import static java.time.temporal.ChronoUnit.HOURS; |
| 31 | +import static java.time.temporal.ChronoUnit.MINUTES; |
31 | 32 | import static net.solarnetwork.central.datum.domain.DatumValidationType.TIME_GAP_VALIDATION_TYPE; |
32 | 33 | import static net.solarnetwork.central.domain.BasicClaimableJobState.Claimed; |
33 | 34 | import static net.solarnetwork.central.domain.BasicClaimableJobState.Executing; |
|
40 | 41 | import static net.solarnetwork.util.NumberUtils.decimalArray; |
41 | 42 | import static org.assertj.core.api.BDDAssertions.and; |
42 | 43 | import static org.assertj.core.api.BDDAssertions.from; |
| 44 | +import static org.assertj.core.api.BDDAssertions.thenExceptionOfType; |
43 | 45 | import static org.mockito.ArgumentMatchers.any; |
44 | 46 | import static org.mockito.ArgumentMatchers.argThat; |
45 | 47 | import static org.mockito.ArgumentMatchers.eq; |
|
60 | 62 | import java.util.UUID; |
61 | 63 | import java.util.concurrent.Callable; |
62 | 64 | import java.util.concurrent.CompletableFuture; |
| 65 | +import java.util.concurrent.ExecutionException; |
63 | 66 | import java.util.concurrent.ExecutorService; |
64 | 67 | import java.util.concurrent.Future; |
65 | 68 | import java.util.concurrent.RejectedExecutionException; |
|
71 | 74 | import org.mockito.Captor; |
72 | 75 | import org.mockito.Mock; |
73 | 76 | import org.mockito.junit.jupiter.MockitoExtension; |
| 77 | +import org.springframework.http.HttpHeaders; |
| 78 | +import org.springframework.http.HttpStatus; |
| 79 | +import org.springframework.web.client.HttpClientErrorException; |
74 | 80 | import net.solarnetwork.central.biz.UserEventAppenderBiz; |
75 | 81 | import net.solarnetwork.central.c2c.biz.CloudDatumStreamService; |
76 | 82 | import net.solarnetwork.central.c2c.biz.impl.DaoCloudDatumStreamRakeService; |
@@ -1356,4 +1362,84 @@ public void executeTask_afterPollStartDate() throws Exception { |
1356 | 1362 | // @formatter:on |
1357 | 1363 | } |
1358 | 1364 |
|
| 1365 | + @Test |
| 1366 | + public void executeTask_http429() throws Exception { |
| 1367 | + // GIVEN |
| 1368 | + // submit task |
| 1369 | + var future = new CompletableFuture<CloudDatumStreamRakeTaskEntity>(); |
| 1370 | + given(executor.submit(argThat((Callable<CloudDatumStreamRakeTaskEntity> call) -> { |
| 1371 | + try { |
| 1372 | + future.complete(call.call()); |
| 1373 | + } catch ( Exception e ) { |
| 1374 | + future.completeExceptionally(e); |
| 1375 | + } |
| 1376 | + return true; |
| 1377 | + }))).willReturn(future); |
| 1378 | + |
| 1379 | + final Instant sod = clock.instant().truncatedTo(ChronoUnit.DAYS); |
| 1380 | + |
| 1381 | + final CloudDatumStreamConfiguration datumStream = new CloudDatumStreamConfiguration(TEST_USER_ID, |
| 1382 | + randomLong(), now(), randomString(), TEST_DATUM_STREAM_SERVICE_IDENTIFIER, |
| 1383 | + ObjectDatumKind.Node); |
| 1384 | + datumStream.setDatumStreamMappingId(randomLong()); |
| 1385 | + datumStream.setSchedule("0 0/5 * * * *"); |
| 1386 | + datumStream.setObjectId(randomLong()); |
| 1387 | + datumStream.setSourceId(randomString()); |
| 1388 | + |
| 1389 | + // look up datum stream associated with task |
| 1390 | + given(datumStreamDao.get(datumStream.getId())).willReturn(datumStream); |
| 1391 | + |
| 1392 | + // verify node ownership |
| 1393 | + final var nodeOwner = new BasicSolarNodeOwnership(datumStream.getObjectId(), TEST_USER_ID, "NZ", |
| 1394 | + UTC, true, false); |
| 1395 | + given(nodeOwnershipDao.ownershipForNodeId(datumStream.getObjectId())).willReturn(nodeOwner); |
| 1396 | + |
| 1397 | + // load poll task to check its start date |
| 1398 | + final CloudDatumStreamPollTaskEntity pollTask = new CloudDatumStreamPollTaskEntity( |
| 1399 | + datumStream.getId(), Claimed, clock.instant(), clock.instant().truncatedTo(HOURS)); |
| 1400 | + given(pollTaskDao.get(datumStream.getId())).willReturn(pollTask); |
| 1401 | + |
| 1402 | + // update task state to "processing" |
| 1403 | + given(taskDao.updateTaskState(datumStream.getId(), Executing, Claimed)).willReturn(true); |
| 1404 | + |
| 1405 | + // query for data associated with service configured on datum stream |
| 1406 | + final var ex = HttpClientErrorException.create(HttpStatus.TOO_MANY_REQUESTS, |
| 1407 | + "Too many requests.", new HttpHeaders(), null, null); |
| 1408 | + given(datumStreamService.datum(same(datumStream), any())).willThrow(ex); |
| 1409 | + |
| 1410 | + // WHEN |
| 1411 | + var task = new CloudDatumStreamRakeTaskEntity(datumStream.getId(), now(), |
| 1412 | + datumStream.getConfigId(), Claimed, sod, Period.ofDays(1)); |
| 1413 | + |
| 1414 | + Future<CloudDatumStreamRakeTaskEntity> result = service.executeTask(task); |
| 1415 | + thenExceptionOfType(ExecutionException.class).isThrownBy(() -> { |
| 1416 | + result.get(1, TimeUnit.MINUTES); |
| 1417 | + }).withCauseInstanceOf(HttpClientErrorException.class); |
| 1418 | + |
| 1419 | + // THEN |
| 1420 | + // @formatter:off |
| 1421 | + then(taskDao).should().updateTask(taskCaptor.capture(), eq(Executing)); |
| 1422 | + and.then(taskCaptor.getValue()) |
| 1423 | + .as("Task to update is copy of given task") |
| 1424 | + .isNotSameAs(task) |
| 1425 | + .as("Task to update has same ID as given task") |
| 1426 | + .isEqualTo(task) |
| 1427 | + .as("Update task state to Queued to run again") |
| 1428 | + .returns(Queued, from(CloudDatumStreamRakeTaskEntity::getState)) |
| 1429 | + .as("Task execute date pushed by one minute") |
| 1430 | + .returns(clock.instant().plus(1, MINUTES), from(CloudDatumStreamRakeTaskEntity::getExecuteAt)) |
| 1431 | + .as("Message generated for failed execution") |
| 1432 | + .returns("Error executing rake task.", from(CloudDatumStreamRakeTaskEntity::getMessage)) |
| 1433 | + .as("Service properties generated for failed execution") |
| 1434 | + .returns(Map.of( |
| 1435 | + CONFIG_ID_DATA_KEY, task.getDatumStreamId(), |
| 1436 | + CONFIG_SUB_ID_DATA_KEY, task.getConfigId(), |
| 1437 | + ERROR_COUNT_DATA_KEY, 0L, |
| 1438 | + MESSAGE_DATA_KEY, ex.getMessage() |
| 1439 | + ), from(CloudDatumStreamRakeTaskEntity::getServiceProperties)) |
| 1440 | + ; |
| 1441 | + |
| 1442 | + // @formatter:on |
| 1443 | + } |
| 1444 | + |
1359 | 1445 | } |
0 commit comments