fix(cloudwatch): return empty CompositeAlarms and LogAlarms from DescribeAlarms (#6363)

* fix(cloudwatch): return empty CompositeAlarms and LogAlarms from DescribeAlarms

AWS sends every alarm collection even when it is empty. Floci only sent
MetricAlarms, so the AWS CLI's text output printed a leading None line
for each missing paginator result key.

Closes #6362

* test(cloudwatch): check empty alarm collections in sdk-test-python instead of bats

Drop the new AWS CLI bats file and assert the empty CompositeAlarms and
LogAlarms lists in the existing describe_alarms test, as boto3 leaves an
absent list out of the response.

(cherry picked from commit 42f5019231b446c727d6c94def9d1445263de2d7)
This commit is contained in:
Stuart Lang
2026-10-24 00:06:44 +01:00
committed by Zakaria
parent 24314f4432
commit 83aafc0325
5 changed files with 33 additions and 0 deletions
@@ -350,6 +350,22 @@ class CloudWatchMetricsJsonHandlerTest {
assertEquals("Count", alarm.get("Unit").asText());
}
// AWS sends every alarm collection even when it is empty. The CLI's text output writes a
// None line for each paginator result key missing from the page (floci-io/floci#6362).
@Test
void describeAlarms_returnsEmptyCompositeAndLogAlarmCollections() {
handler.handle("PutMetricAlarm", alarmRequest("MetricOnly"), REGION);
ObjectNode response = (ObjectNode) handler.handle("DescribeAlarms", MAPPER.createObjectNode(), REGION)
.getEntity();
assertEquals(1, response.get("MetricAlarms").size());
assertTrue(response.get("CompositeAlarms").isArray());
assertTrue(response.get("CompositeAlarms").isEmpty());
assertTrue(response.get("LogAlarms").isArray());
assertTrue(response.get("LogAlarms").isEmpty());
}
// An alarm the caller never gave an M for reports no DatapointsToAlarm at all. AWS omits the
// member in that case, and echoing EvaluationPeriods instead reads as a change to any client
// that re-plans against its own configuration, which is what floci-io/floci#3660 hit.
@@ -242,4 +242,15 @@ class CloudWatchMetricsQueryHandlerTest {
verify(metricsService).putMetricData(eq("NS"), captor.capture(), eq(REGION));
assertEquals(List.of(" "), captor.getValue().stream().map(MetricDatum::getMetricName).toList());
}
@Test
void describeAlarmsReturnsEmptyCompositeAndLogAlarmCollections() {
when(metricsService.describeAlarms(any(), any(), eq(REGION))).thenReturn(List.of());
String xml = (String) handler.handle("DescribeAlarms", new MultivaluedHashMap<>(), REGION).getEntity();
assertTrue(xml.contains("<MetricAlarms></MetricAlarms>"), xml);
assertTrue(xml.contains("<CompositeAlarms></CompositeAlarms>"), xml);
assertTrue(xml.contains("<LogAlarms></LogAlarms>"), xml);
}
}