@@ -263,6 +263,65 @@ void shouldAddMetadata() throws Exception {
263263 assertThat (rootNode .get ("CustomMetadata" ).asText ()).isEqualTo ("MetadataValue" );
264264 }
265265
266+ @ Test
267+ void shouldClearMetadataAfterFlush () throws Exception {
268+ // Given - Add metadata and flush first time
269+ metrics .addMetadata ("RequestId" , "req-123" );
270+ metrics .addMetadata ("UserAgent" , "test-agent" );
271+ metrics .addMetric ("FirstMetric" , 1.0 );
272+ metrics .flush ();
273+
274+ // Capture first flush output and reset for second flush
275+ String firstFlushOutput = outputStreamCaptor .toString ().trim ();
276+ outputStreamCaptor .reset ();
277+
278+ // When - Add another metric and flush again using the SAME metrics instance
279+ metrics .addMetric ("SecondMetric" , 2.0 );
280+ metrics .flush ();
281+
282+ // Then - Verify first flush had metadata
283+ JsonNode firstRootNode = objectMapper .readTree (firstFlushOutput );
284+ assertThat (firstRootNode .has ("RequestId" )).isTrue ();
285+ assertThat (firstRootNode .get ("RequestId" ).asText ()).isEqualTo ("req-123" );
286+ assertThat (firstRootNode .has ("UserAgent" )).isTrue ();
287+ assertThat (firstRootNode .get ("UserAgent" ).asText ()).isEqualTo ("test-agent" );
288+ assertThat (firstRootNode .has ("FirstMetric" )).isTrue ();
289+
290+ // Verify second flush does NOT have metadata from first flush
291+ // The EMF library automatically clears metadata after flush
292+ String secondFlushOutput = outputStreamCaptor .toString ().trim ();
293+ JsonNode secondRootNode = objectMapper .readTree (secondFlushOutput );
294+
295+ // Metadata should be cleared after first flush by the EMF library
296+ assertThat (secondRootNode .has ("RequestId" )).isFalse ();
297+ assertThat (secondRootNode .has ("UserAgent" )).isFalse ();
298+ assertThat (secondRootNode .has ("SecondMetric" )).isTrue ();
299+ }
300+
301+ @ Test
302+ void shouldInheritMetadataInFlushMetricsMethod () throws Exception {
303+ // Given - Add metadata to the main metrics instance
304+ metrics .addMetadata ("PersistentMetadata" , "should-inherit" );
305+ metrics .addMetadata ("GlobalContext" , "main-instance" );
306+
307+ // When - Use flushMetrics to create a separate metrics context
308+ metrics .flushMetrics (separateMetrics -> {
309+ separateMetrics .addMetric ("SeparateMetric" , 1.0 );
310+ // Don't add any metadata to the separate instance
311+ });
312+
313+ // Then - The separate metrics context SHOULD inherit metadata from main instance
314+ String flushMetricsOutput = outputStreamCaptor .toString ().trim ();
315+ JsonNode rootNode = objectMapper .readTree (flushMetricsOutput );
316+
317+ // The separate metrics should have inherited metadata (this is expected behavior)
318+ assertThat (rootNode .has ("PersistentMetadata" )).isTrue ();
319+ assertThat (rootNode .get ("PersistentMetadata" ).asText ()).isEqualTo ("should-inherit" );
320+ assertThat (rootNode .has ("GlobalContext" )).isTrue ();
321+ assertThat (rootNode .get ("GlobalContext" ).asText ()).isEqualTo ("main-instance" );
322+ assertThat (rootNode .has ("SeparateMetric" )).isTrue ();
323+ }
324+
266325 @ Test
267326 void shouldSetDefaultDimensions () throws Exception {
268327 // Given
@@ -547,4 +606,134 @@ void shouldNotFlushSingleMetricWhenDisabled() {
547606 String emfOutput = outputStreamCaptor .toString ().trim ();
548607 assertThat (emfOutput ).isEmpty ();
549608 }
609+
610+ @ Test
611+ void shouldClearCustomDimensionsAfterFlush () throws Exception {
612+ // Given - Set up default dimensions that should persist
613+ DimensionSet defaultDimensions = DimensionSet .of ("Service" , "TestService" , "Environment" , "Test" );
614+ metrics .setDefaultDimensions (defaultDimensions );
615+
616+ // First invocation - add custom dimensions and flush
617+ DimensionSet customDimensions = DimensionSet .of ("EXAMPLE_KEY" , "EXAMPLE_VALUE" );
618+ metrics .addDimension (customDimensions );
619+ metrics .addMetric ("SERL" , 1.0 );
620+ metrics .flush ();
621+
622+ // Capture first flush output
623+ String firstFlushOutput = outputStreamCaptor .toString ().trim ();
624+ outputStreamCaptor .reset (); // Clear for second flush
625+
626+ // Second invocation - should NOT have custom dimensions from first invocation
627+ metrics .addMetric ("Expected" , 1.0 );
628+ metrics .flush ();
629+
630+ // Then - Verify first flush had both default and custom dimensions
631+ JsonNode firstRootNode = objectMapper .readTree (firstFlushOutput );
632+ assertThat (firstRootNode .has ("Service" )).isTrue ();
633+ assertThat (firstRootNode .get ("Service" ).asText ()).isEqualTo ("TestService" );
634+ assertThat (firstRootNode .has ("Environment" )).isTrue ();
635+ assertThat (firstRootNode .get ("Environment" ).asText ()).isEqualTo ("Test" );
636+ assertThat (firstRootNode .has ("EXAMPLE_KEY" )).isTrue ();
637+ assertThat (firstRootNode .get ("EXAMPLE_KEY" ).asText ()).isEqualTo ("EXAMPLE_VALUE" );
638+ assertThat (firstRootNode .has ("SERL" )).isTrue ();
639+
640+ // Verify second flush has ONLY default dimensions (custom dimensions should be cleared)
641+ String secondFlushOutput = outputStreamCaptor .toString ().trim ();
642+ JsonNode secondRootNode = objectMapper .readTree (secondFlushOutput );
643+
644+ // Default dimensions should still be present
645+ assertThat (secondRootNode .has ("Service" )).isTrue ();
646+ assertThat (secondRootNode .get ("Service" ).asText ()).isEqualTo ("TestService" );
647+ assertThat (secondRootNode .has ("Environment" )).isTrue ();
648+ assertThat (secondRootNode .get ("Environment" ).asText ()).isEqualTo ("Test" );
649+
650+ // Custom dimensions should be cleared (this is the failing assertion that demonstrates the bug)
651+ assertThat (secondRootNode .has ("EXAMPLE_KEY" )).isFalse ();
652+ assertThat (secondRootNode .has ("Expected" )).isTrue ();
653+
654+ // Verify dimensions in CloudWatchMetrics section
655+ JsonNode secondDimensions = secondRootNode .get ("_aws" ).get ("CloudWatchMetrics" ).get (0 ).get ("Dimensions" ).get (0 );
656+ boolean hasExampleKey = false ;
657+ boolean hasService = false ;
658+ boolean hasEnvironment = false ;
659+
660+ for (JsonNode dimension : secondDimensions ) {
661+ String dimName = dimension .asText ();
662+ if ("EXAMPLE_KEY" .equals (dimName )) {
663+ hasExampleKey = true ;
664+ } else if ("Service" .equals (dimName )) {
665+ hasService = true ;
666+ } else if ("Environment" .equals (dimName )) {
667+ hasEnvironment = true ;
668+ }
669+ }
670+
671+ // Default dimensions should be in CloudWatchMetrics
672+ assertThat (hasService ).isTrue ();
673+ assertThat (hasEnvironment ).isTrue ();
674+ // Custom dimension should NOT be in CloudWatchMetrics (this should fail initially)
675+ assertThat (hasExampleKey ).isFalse ();
676+ }
677+
678+ @ Test
679+ void shouldHandleEmptyCustomDimensionsGracefully () throws Exception {
680+ // Given - Only default dimensions, no custom dimensions
681+ metrics .setDefaultDimensions (DimensionSet .of ("Service" , "TestService" ));
682+
683+ // When - Flush without adding custom dimensions
684+ metrics .addMetric ("TestMetric" , 1.0 );
685+ metrics .flush ();
686+ outputStreamCaptor .reset ();
687+
688+ // Second flush
689+ metrics .addMetric ("TestMetric2" , 2.0 );
690+ metrics .flush ();
691+
692+ // Then - Should work normally with only default dimensions
693+ String output = outputStreamCaptor .toString ().trim ();
694+ JsonNode rootNode = objectMapper .readTree (output );
695+
696+ assertThat (rootNode .has ("Service" )).isTrue ();
697+ assertThat (rootNode .get ("Service" ).asText ()).isEqualTo ("TestService" );
698+ assertThat (rootNode .has ("TestMetric2" )).isTrue ();
699+ }
700+
701+ @ Test
702+ void shouldClearCustomDimensionsWhenNoDefaultDimensionsSet () throws Exception {
703+ // Given - No default dimensions set
704+ metrics .clearDefaultDimensions ();
705+
706+ // When - Add custom dimensions and flush
707+ metrics .addDimension ("CustomDim" , "CustomValue" );
708+ metrics .addMetric ("Metric1" , 1.0 );
709+ metrics .flush ();
710+ outputStreamCaptor .reset ();
711+
712+ // Second flush without custom dimensions
713+ metrics .addMetric ("Metric2" , 2.0 );
714+ metrics .flush ();
715+
716+ // Then - Custom dimensions should be cleared
717+ String output = outputStreamCaptor .toString ().trim ();
718+ JsonNode rootNode = objectMapper .readTree (output );
719+
720+ assertThat (rootNode .has ("CustomDim" )).isFalse ();
721+ assertThat (rootNode .has ("Metric2" )).isTrue ();
722+
723+ // Verify no custom dimensions in CloudWatchMetrics section
724+ JsonNode dimensionsArray = rootNode .get ("_aws" ).get ("CloudWatchMetrics" ).get (0 ).get ("Dimensions" );
725+ boolean hasCustomDim = false ;
726+ if (dimensionsArray != null && dimensionsArray .size () > 0 ) {
727+ JsonNode dimensions = dimensionsArray .get (0 );
728+ if (dimensions != null ) {
729+ for (JsonNode dimension : dimensions ) {
730+ if ("CustomDim" .equals (dimension .asText ())) {
731+ hasCustomDim = true ;
732+ break ;
733+ }
734+ }
735+ }
736+ }
737+ assertThat (hasCustomDim ).isFalse ();
738+ }
550739}
0 commit comments