From 918b50af93870f5a16e965c73a84bec5a9a59c93 Mon Sep 17 00:00:00 2001 From: Albert Hsu <45705038+iverson52000@users.noreply.github.com> Date: Mon, 4 Mar 2024 13:28:15 -0800 Subject: [PATCH] Update cross-media-measurement-api dep for CustomMaximumFrequencyPerUser in DeterministicCount methodology (#1462) For API change in https://github.com/world-federation-of-advertisers/cross-media-measurement-api/pull/197 --- MODULE.bazel | 2 +- MODULE.bazel.lock | 54 ++----- .../service/api/v2alpha/ProtoConversions.kt | 6 +- .../MeasurementConsumerSimulator.kt | 9 +- .../service/api/v2alpha/MetricsService.kt | 9 +- .../service/api/v2alpha/ProtoConversions.kt | 6 +- .../reporting/v2/direct_computation.proto | 7 +- .../loadtest/dataprovider/EdpSimulatorTest.kt | 4 +- .../service/api/v2alpha/MetricsServiceTest.kt | 138 ++++++++++++++++++ 9 files changed, 183 insertions(+), 52 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index 878a3e2e7c..a44fa06cc4 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -136,7 +136,7 @@ bazel_dep( ) bazel_dep( name = "cross-media-measurement-api", - version = "0.58.0", + version = "0.60.0", repo_name = "wfa_measurement_proto", ) bazel_dep( diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 30ce1e3b32..f3b2e150d9 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -1,6 +1,6 @@ { "lockFileVersion": 3, - "moduleFileHash": "a9078c95fd7c079ddaecae4d8e09e2187f32ab2d26b24b982cd4c5a85f8b2d0e", + "moduleFileHash": "32b1eb6320978c3c0532bec3923e10eeb31f6e9e10d2c77f821464d9f40953a1", "flags": { "cmdRegistries": [ "https://raw.githubusercontent.com/world-federation-of-advertisers/bazel-registry/main", @@ -377,7 +377,7 @@ "wfa_rules_cue": "rules_cue@0.4.0", "wfa_common_jvm": "common-jvm@0.75.0", "wfa_common_cpp": "common-cpp@0.12.0", - "wfa_measurement_proto": "cross-media-measurement-api@0.58.0", + "wfa_measurement_proto": "cross-media-measurement-api@0.60.0", "wfa_consent_signaling_client": "consent-signaling-client@0.20.0", "any_sketch": "any-sketch@0.6.0", "any_sketch_java": "any-sketch-java@0.5.0", @@ -2580,10 +2580,10 @@ } } }, - "cross-media-measurement-api@0.58.0": { + "cross-media-measurement-api@0.60.0": { "name": "cross-media-measurement-api", - "version": "0.58.0", - "key": "cross-media-measurement-api@0.58.0", + "version": "0.60.0", + "key": "cross-media-measurement-api@0.60.0", "repoName": "wfa_measurement_proto", "executionPlatformsToRegister": [], "toolchainsToRegister": [], @@ -2591,9 +2591,9 @@ { "extensionBzlFile": "@wfa_measurement_proto//build:non_module_deps.bzl", "extensionName": "non_module_deps", - "usingModule": "cross-media-measurement-api@0.58.0", + "usingModule": "cross-media-measurement-api@0.60.0", "location": { - "file": "https://raw.githubusercontent.com/world-federation-of-advertisers/bazel-registry/main/modules/cross-media-measurement-api/0.58.0/MODULE.bazel", + "file": "https://raw.githubusercontent.com/world-federation-of-advertisers/bazel-registry/main/modules/cross-media-measurement-api/0.60.0/MODULE.bazel", "line": 25, "column": 32 }, @@ -2617,14 +2617,14 @@ "bzlFile": "@bazel_tools//tools/build_defs/repo:http.bzl", "ruleClassName": "http_archive", "attributes": { - "name": "cross-media-measurement-api~0.58.0", + "name": "cross-media-measurement-api~0.60.0", "urls": [ - "https://github.com/world-federation-of-advertisers/cross-media-measurement-api/archive/refs/tags/v0.58.0.tar.gz" + "https://github.com/world-federation-of-advertisers/cross-media-measurement-api/archive/refs/tags/v0.60.0.tar.gz" ], - "integrity": "sha256-RW331f5a63glIZLY1isEfq7jZTlUl3zGrYciqWYjRY8=", - "strip_prefix": "cross-media-measurement-api-0.58.0", + "integrity": "sha256-m9CFeMyEBWFe5lENTk7tT2e3trguJvfteLU+QcWjZQ4=", + "strip_prefix": "cross-media-measurement-api-0.60.0", "remote_patches": { - "https://raw.githubusercontent.com/world-federation-of-advertisers/bazel-registry/main/modules/cross-media-measurement-api/0.58.0/patches/module_dot_bazel.patch": "sha256-L/DJ0VFL33xfvGf2VBMt8v42P6cWjUg5zGIakHK6KdE=" + "https://raw.githubusercontent.com/world-federation-of-advertisers/bazel-registry/main/modules/cross-media-measurement-api/0.60.0/patches/module_dot_bazel.patch": "sha256-/0FKi8ly/uVbEhju1Jt/h+4baPJNSCeyPvM+N2IoNeU=" }, "remote_patch_strip": 0 } @@ -2640,7 +2640,7 @@ "extensionUsages": [], "deps": { "wfa_rules_kotlin_jvm": "rules_kotlin_jvm@0.2.0", - "wfa_measurement_proto": "cross-media-measurement-api@0.58.0", + "wfa_measurement_proto": "cross-media-measurement-api@0.60.0", "wfa_common_jvm": "common-jvm@0.75.0", "bazel_tools": "bazel_tools@_", "local_config_platform": "local_config_platform@_" @@ -136103,34 +136103,6 @@ "recordedRepoMappingEntries": [] } }, - "@@cross-media-measurement-api~0.58.0//build:non_module_deps.bzl%non_module_deps": { - "general": { - "bzlTransitiveDigest": "a9q7X8lfWz0lA6aCm7Qg0Aull4JzF4fTtyrkt4sFst4=", - "accumulatedFileDigests": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "plantuml": { - "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", - "ruleClassName": "http_file", - "attributes": { - "name": "cross-media-measurement-api~0.58.0~non_module_deps~plantuml", - "downloaded_file_path": "plantuml.jar", - "sha256": "3a659c3d87ea5ebac7aadb645233176c51d0290777ebc28285dd2a35dc947752", - "urls": [ - "https://github.com/plantuml/plantuml/releases/download/v1.2023.4/plantuml-1.2023.4.jar" - ] - } - } - }, - "recordedRepoMappingEntries": [ - [ - "cross-media-measurement-api~0.58.0", - "bazel_tools", - "bazel_tools" - ] - ] - } - }, "@@gazelle~0.34.0//:extensions.bzl%go_deps": { "general": { "bzlTransitiveDigest": "bxCC2wkQKI2fVRS2z8qAkH7FpoBFV1H7+yyT3EXXdbU=", diff --git a/src/main/kotlin/org/wfanet/measurement/kingdom/service/api/v2alpha/ProtoConversions.kt b/src/main/kotlin/org/wfanet/measurement/kingdom/service/api/v2alpha/ProtoConversions.kt index 8c8c5cf08a..66207c2129 100644 --- a/src/main/kotlin/org/wfanet/measurement/kingdom/service/api/v2alpha/ProtoConversions.kt +++ b/src/main/kotlin/org/wfanet/measurement/kingdom/service/api/v2alpha/ProtoConversions.kt @@ -162,9 +162,9 @@ val DEFAULT_DIRECT_REACH_PROTOCOL_CONFIG: ProtocolConfig.Direct = direct { } /** - * Default direct reach-and-freqeuncy protocol config for backward compatibility. + * Default direct reach-and-frequency protocol config for backward compatibility. * - * Used when existing direct protocol configs of reach-and-freqeuncy measurements don't have + * Used when existing direct protocol configs of reach-and-frequency measurements don't have * methodologies. */ val DEFAULT_DIRECT_REACH_AND_FREQUENCY_PROTOCOL_CONFIG: ProtocolConfig.Direct = direct { @@ -838,7 +838,7 @@ fun ModelShard.toInternal( } } -/** Converts an internal [InternaPopulation] to a public [Population]. */ +/** Converts an internal [InternalPopulation] to a public [Population]. */ fun InternalPopulation.toPopulation(): Population { val source = this diff --git a/src/main/kotlin/org/wfanet/measurement/loadtest/measurementconsumer/MeasurementConsumerSimulator.kt b/src/main/kotlin/org/wfanet/measurement/loadtest/measurementconsumer/MeasurementConsumerSimulator.kt index c246448e7a..0a49b0c4ea 100644 --- a/src/main/kotlin/org/wfanet/measurement/loadtest/measurementconsumer/MeasurementConsumerSimulator.kt +++ b/src/main/kotlin/org/wfanet/measurement/loadtest/measurementconsumer/MeasurementConsumerSimulator.kt @@ -515,6 +515,13 @@ class MeasurementConsumerSimulator( val measurementComputationInfo: MeasurementComputationInfo = buildMeasurementComputationInfo(protocol, result.impression.noiseMechanism) + val maxFrequencyPerUser = + if (result.impression.deterministicCount.customMaximumFrequencyPerUser != 0) { + result.impression.deterministicCount.customMaximumFrequencyPerUser + } else { + measurementSpec.impression.maximumFrequencyPerUser + } + return VariancesImpl.computeMeasurementVariance( measurementComputationInfo.methodology, ImpressionMeasurementVarianceParams( @@ -523,7 +530,7 @@ class MeasurementConsumerSimulator( ImpressionMeasurementParams( vidSamplingInterval = measurementSpec.vidSamplingInterval.toStatsVidSamplingInterval(), dpParams = measurementSpec.impression.privacyParams.toNoiserDpParams(), - maximumFrequencyPerUser = measurementSpec.impression.maximumFrequencyPerUser, + maximumFrequencyPerUser = maxFrequencyPerUser, noiseMechanism = measurementComputationInfo.noiseMechanism.toStatsNoiseMechanism(), ), ), diff --git a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricsService.kt b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricsService.kt index 87bb6876b6..764d21713b 100644 --- a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricsService.kt +++ b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricsService.kt @@ -2094,6 +2094,13 @@ fun buildWeightedImpressionMeasurementVarianceParamsPerResult( return@map null } + val maxFrequencyPerUser = + if (impressionResult.deterministicCount.customMaximumFrequencyPerUser != 0) { + impressionResult.deterministicCount.customMaximumFrequencyPerUser + } else { + metricSpec.impressionCount.maximumFrequencyPerUser + } + WeightedImpressionMeasurementVarianceParams( binaryRepresentation = weightedMeasurement.binaryRepresentation, weight = weightedMeasurement.weight, @@ -2104,7 +2111,7 @@ fun buildWeightedImpressionMeasurementVarianceParamsPerResult( ImpressionMeasurementParams( vidSamplingInterval = metricSpec.vidSamplingInterval.toStatsVidSamplingInterval(), dpParams = metricSpec.impressionCount.privacyParams.toNoiserDpParams(), - maximumFrequencyPerUser = metricSpec.impressionCount.maximumFrequencyPerUser, + maximumFrequencyPerUser = maxFrequencyPerUser, noiseMechanism = statsNoiseMechanism, ), ), diff --git a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/ProtoConversions.kt b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/ProtoConversions.kt index 746c90669b..81349a4b67 100644 --- a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/ProtoConversions.kt +++ b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/ProtoConversions.kt @@ -43,7 +43,6 @@ import org.wfanet.measurement.config.reporting.MetricSpecConfig import org.wfanet.measurement.eventdataprovider.noiser.DpParams as NoiserDpParams import org.wfanet.measurement.internal.reporting.v2.CustomDirectMethodology as InternalCustomDirectMethodology import org.wfanet.measurement.internal.reporting.v2.CustomDirectMethodologyKt as InternalCustomDirectMethodologyKt -import org.wfanet.measurement.internal.reporting.v2.DeterministicCount import org.wfanet.measurement.internal.reporting.v2.DeterministicCountDistinct import org.wfanet.measurement.internal.reporting.v2.DeterministicDistribution import org.wfanet.measurement.internal.reporting.v2.DeterministicSum @@ -69,6 +68,7 @@ import org.wfanet.measurement.internal.reporting.v2.StreamReportsRequest import org.wfanet.measurement.internal.reporting.v2.StreamReportsRequestKt import org.wfanet.measurement.internal.reporting.v2.TimeIntervals as InternalTimeIntervals import org.wfanet.measurement.internal.reporting.v2.customDirectMethodology +import org.wfanet.measurement.internal.reporting.v2.deterministicCount import org.wfanet.measurement.internal.reporting.v2.liquidLegionsCountDistinct import org.wfanet.measurement.internal.reporting.v2.liquidLegionsDistribution import org.wfanet.measurement.internal.reporting.v2.liquidLegionsSketchParams @@ -467,7 +467,9 @@ private fun Measurement.Result.Impression.toInternal( customDirectMethodology = source.customDirectMethodology.toInternal() } Measurement.Result.Impression.MethodologyCase.DETERMINISTIC_COUNT -> { - deterministicCount = DeterministicCount.getDefaultInstance() + deterministicCount = deterministicCount { + customMaximumFrequencyPerUser = source.deterministicCount.customMaximumFrequencyPerUser + } } } } else { diff --git a/src/main/proto/wfa/measurement/internal/reporting/v2/direct_computation.proto b/src/main/proto/wfa/measurement/internal/reporting/v2/direct_computation.proto index 0618b94431..e444c9698e 100644 --- a/src/main/proto/wfa/measurement/internal/reporting/v2/direct_computation.proto +++ b/src/main/proto/wfa/measurement/internal/reporting/v2/direct_computation.proto @@ -81,7 +81,12 @@ message DeterministicCountDistinct {} message DeterministicDistribution {} // Parameters used when applying the deterministic count methodology. -message DeterministicCount {} +message DeterministicCount { + // Custom maximum frequency per user value calculated by the + // DataProvider. If this is specified, the maximum_frequency_per_user in + // measurement_spec will be ignored. + int32 custom_maximum_frequency_per_user = 1; +} // Parameters used when applying the deterministic sum methodology. message DeterministicSum {} diff --git a/src/test/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulatorTest.kt b/src/test/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulatorTest.kt index ade395a78c..ff83ebe8e4 100644 --- a/src/test/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulatorTest.kt +++ b/src/test/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulatorTest.kt @@ -2372,7 +2372,7 @@ class EdpSimulatorTest { } @Test - fun `fails to fulfill impression Requisition when no direct noise mechanism is picked by EDP`() { + fun `fails to fulfill impression Requisition when no direct noise mechanism options are provided by Kingdom`() { val noiseMechanismOption = ProtocolConfig.NoiseMechanism.NONE val requisition = REQUISITION.copy { @@ -2435,7 +2435,7 @@ class EdpSimulatorTest { } @Test - fun `fails to fulfill impression Requisition when no direct methodology is picked by EDP`() { + fun `fails to fulfill impression Requisition when no direct methodologies are provided by Kingdom`() { val noiseMechanismOption = ProtocolConfig.NoiseMechanism.CONTINUOUS_GAUSSIAN val requisition = REQUISITION.copy { diff --git a/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricsServiceTest.kt b/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricsServiceTest.kt index 65177023a4..e264e6eb21 100644 --- a/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricsServiceTest.kt +++ b/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricsServiceTest.kt @@ -96,6 +96,7 @@ import org.wfanet.measurement.api.v2alpha.copy import org.wfanet.measurement.api.v2alpha.createMeasurementRequest import org.wfanet.measurement.api.v2alpha.customDirectMethodology import org.wfanet.measurement.api.v2alpha.dataProvider +import org.wfanet.measurement.api.v2alpha.deterministicCount import org.wfanet.measurement.api.v2alpha.differentialPrivacyParams import org.wfanet.measurement.api.v2alpha.encryptionPublicKey import org.wfanet.measurement.api.v2alpha.getCertificateRequest @@ -189,6 +190,7 @@ import org.wfanet.measurement.internal.reporting.v2.batchSetMeasurementResultsRe import org.wfanet.measurement.internal.reporting.v2.copy import org.wfanet.measurement.internal.reporting.v2.createMetricRequest as internalCreateMetricRequest import org.wfanet.measurement.internal.reporting.v2.customDirectMethodology as internalCustomDirectMethodology +import org.wfanet.measurement.internal.reporting.v2.deterministicCount as internalDeterministicCount import org.wfanet.measurement.internal.reporting.v2.liquidLegionsDistribution as internalLiquidLegionsDistribution import org.wfanet.measurement.internal.reporting.v2.measurement as internalMeasurement import org.wfanet.measurement.internal.reporting.v2.metric as internalMetric @@ -258,6 +260,7 @@ private const val IMPRESSION_VID_SAMPLING_WIDTH = 62.0f / NUMBER_VID_BUCKETS private const val IMPRESSION_VID_SAMPLING_START = 143.0f / NUMBER_VID_BUCKETS private const val IMPRESSION_EPSILON = 0.0011 private const val IMPRESSION_MAXIMUM_FREQUENCY_PER_USER = 60 +private const val IMPRESSION_CUSTOM_MAXIMUM_FREQUENCY_PER_USER = 100 private const val WATCH_DURATION_VID_SAMPLING_WIDTH = 95.0f / NUMBER_VID_BUCKETS private const val WATCH_DURATION_VID_SAMPLING_START = 205.0f / NUMBER_VID_BUCKETS @@ -827,6 +830,25 @@ private val INTERNAL_SUCCEEDED_SINGLE_PUBLISHER_IMPRESSION_MEASUREMENT = } } +private val INTERNAL_SUCCEEDED_SINGLE_PUBLISHER_IMPRESSION_MEASUREMENT_CUSTOM_CAP = + INTERNAL_PENDING_SINGLE_PUBLISHER_IMPRESSION_MEASUREMENT.copy { + state = InternalMeasurement.State.SUCCEEDED + details = + InternalMeasurementKt.details { + results += + InternalMeasurementKt.result { + impression = + InternalMeasurementKt.ResultKt.impression { + value = IMPRESSION_VALUE + noiseMechanism = NoiseMechanism.CONTINUOUS_LAPLACE + deterministicCount = internalDeterministicCount { + customMaximumFrequencyPerUser = IMPRESSION_CUSTOM_MAXIMUM_FREQUENCY_PER_USER + } + } + } + } + } + // Internal cross-publisher watch duration measurements private val INTERNAL_REQUESTING_UNION_ALL_WATCH_DURATION_MEASUREMENT = internalMeasurement { cmmsMeasurementConsumerId = MEASUREMENT_CONSUMERS.keys.first().measurementConsumerId @@ -1195,6 +1217,27 @@ private val SUCCEEDED_SINGLE_PUBLISHER_IMPRESSION_MEASUREMENT = } } +private val SUCCEEDED_SINGLE_PUBLISHER_IMPRESSION_MEASUREMENT_CUSTOM_CAP = + PENDING_SINGLE_PUBLISHER_IMPRESSION_MEASUREMENT.copy { + state = Measurement.State.SUCCEEDED + results += resultOutput { + val result = + MeasurementKt.result { + impression = + MeasurementKt.ResultKt.impression { + value = IMPRESSION_VALUE + noiseMechanism = ProtocolConfig.NoiseMechanism.CONTINUOUS_LAPLACE + deterministicCount = deterministicCount { + customMaximumFrequencyPerUser = IMPRESSION_CUSTOM_MAXIMUM_FREQUENCY_PER_USER + } + } + } + encryptedResult = + encryptResult(signResult(result, AGGREGATOR_SIGNING_KEY), MEASUREMENT_CONSUMER_PUBLIC_KEY) + certificate = AGGREGATOR_CERTIFICATE.name + } + } + // CMMS cross publisher watch duration measurements private val UNION_ALL_WATCH_DURATION_MEASUREMENT_SPEC = measurementSpec { measurementPublicKey = MEASUREMENT_CONSUMER_PUBLIC_KEY.pack() @@ -1606,6 +1649,16 @@ private val INTERNAL_SUCCEEDED_SINGLE_PUBLISHER_IMPRESSION_METRIC = } } +private val INTERNAL_SUCCEEDED_SINGLE_PUBLISHER_IMPRESSION_METRIC_CUSTOM_CAP = + INTERNAL_PENDING_SINGLE_PUBLISHER_IMPRESSION_METRIC.copy { + weightedMeasurements.clear() + weightedMeasurements += weightedMeasurement { + weight = 1 + binaryRepresentation = 1 + measurement = INTERNAL_SUCCEEDED_SINGLE_PUBLISHER_IMPRESSION_MEASUREMENT_CUSTOM_CAP + } + } + // Internal Cross Publisher Watch Duration Metrics private val INTERNAL_REQUESTING_CROSS_PUBLISHER_WATCH_DURATION_METRIC = internalMetric { cmmsMeasurementConsumerId = MEASUREMENT_CONSUMERS.keys.first().measurementConsumerId @@ -6810,6 +6863,91 @@ class MetricsServiceTest { assertThat(result).isEqualTo(SUCCEEDED_SINGLE_PUBLISHER_IMPRESSION_METRIC) } + @Test + fun `getMetric returns impression metric with SUCCEEDED when measurements have custom frequency cap and are updated to SUCCEEDED`() = + runBlocking { + whenever( + internalMetricsMock.batchGetMetrics( + eq( + internalBatchGetMetricsRequest { + cmmsMeasurementConsumerId = + INTERNAL_PENDING_SINGLE_PUBLISHER_IMPRESSION_METRIC.cmmsMeasurementConsumerId + externalMetricIds += + INTERNAL_PENDING_SINGLE_PUBLISHER_IMPRESSION_METRIC.externalMetricId + } + ) + ) + ) + .thenReturn( + internalBatchGetMetricsResponse { + metrics += INTERNAL_PENDING_SINGLE_PUBLISHER_IMPRESSION_METRIC + }, + internalBatchGetMetricsResponse { + metrics += INTERNAL_SUCCEEDED_SINGLE_PUBLISHER_IMPRESSION_METRIC_CUSTOM_CAP + }, + ) + + whenever(measurementsMock.batchGetMeasurements(any())).thenAnswer { + val batchGetMeasurementsRequest = it.arguments[0] as BatchGetMeasurementsRequest + val measurementsMap = + mapOf( + PENDING_SINGLE_PUBLISHER_IMPRESSION_MEASUREMENT.name to + SUCCEEDED_SINGLE_PUBLISHER_IMPRESSION_MEASUREMENT_CUSTOM_CAP + ) + batchGetMeasurementsResponse { + measurements += + batchGetMeasurementsRequest.namesList.map { name -> measurementsMap.getValue(name) } + } + } + + whenever(internalMeasurementsMock.batchSetMeasurementResults(any())) + .thenReturn( + batchSetCmmsMeasurementResultsResponse { + measurements += INTERNAL_SUCCEEDED_SINGLE_PUBLISHER_IMPRESSION_MEASUREMENT_CUSTOM_CAP + } + ) + + val request = getMetricRequest { name = PENDING_SINGLE_PUBLISHER_IMPRESSION_METRIC.name } + + val result = + withMeasurementConsumerPrincipal(MEASUREMENT_CONSUMERS.values.first().name, CONFIG) { + runBlocking { service.getMetric(request) } + } + + // Verify proto argument of internal MeasurementsCoroutineImplBase::batchSetMeasurementResults + val batchSetMeasurementResultsCaptor: KArgumentCaptor = + argumentCaptor() + verifyBlocking(internalMeasurementsMock, times(1)) { + batchSetMeasurementResults(batchSetMeasurementResultsCaptor.capture()) + } + assertThat(batchSetMeasurementResultsCaptor.allValues) + .containsExactly( + batchSetMeasurementResultsRequest { + cmmsMeasurementConsumerId = + INTERNAL_SUCCEEDED_SINGLE_PUBLISHER_IMPRESSION_MEASUREMENT_CUSTOM_CAP + .cmmsMeasurementConsumerId + measurementResults += measurementResult { + cmmsMeasurementId = + INTERNAL_SUCCEEDED_SINGLE_PUBLISHER_IMPRESSION_MEASUREMENT_CUSTOM_CAP + .cmmsMeasurementId + this.results += + INTERNAL_SUCCEEDED_SINGLE_PUBLISHER_IMPRESSION_MEASUREMENT_CUSTOM_CAP.details + .resultsList + } + } + ) + + // Verify proto argument of internal + // MeasurementsCoroutineImplBase::batchSetMeasurementFailures + val batchSetMeasurementFailuresCaptor: KArgumentCaptor = + argumentCaptor() + verifyBlocking(internalMeasurementsMock, never()) { + batchSetMeasurementFailures(batchSetMeasurementFailuresCaptor.capture()) + } + + assertThat(result).isEqualTo(SUCCEEDED_SINGLE_PUBLISHER_IMPRESSION_METRIC) + } + @Test fun `getMetric returns the metric with FAILED when measurements are updated to FAILED`() = runBlocking {