Skip to content

Commit

Permalink
Add and implement KingdomRelationalDatabase.refuseRequisition.
Browse files Browse the repository at this point in the history
Bug: 174685344
Change-Id: Ieed51fb50d60e365081c3df9c925898bfe4a17dc
  • Loading branch information
SanjayVas committed Dec 3, 2020
1 parent 5502b79 commit 19d5e6b
Show file tree
Hide file tree
Showing 5 changed files with 312 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import org.wfanet.measurement.internal.kingdom.ReportDetails
import org.wfanet.measurement.internal.kingdom.ReportLogEntry
import org.wfanet.measurement.internal.kingdom.Requisition
import org.wfanet.measurement.internal.kingdom.Requisition.RequisitionState
import org.wfanet.measurement.internal.kingdom.RequisitionDetails
import org.wfanet.measurement.internal.kingdom.RequisitionTemplate

/**
Expand Down Expand Up @@ -58,6 +59,17 @@ interface KingdomRelationalDatabase {
duchyId: String
): RequisitionUpdate

/**
* Transitions the state of a [Requisition] to
* [RequisitionState.PERMANENTLY_UNAVAILABLE] if its current state is
* [RequisitionState.UNFULFILLED], setting
* [requisition_details.refusal][RequisitionDetails.getRefusal].
*/
suspend fun refuseRequisition(
externalRequisitionId: ExternalId,
refusal: RequisitionDetails.Refusal
): RequisitionUpdate

/**
* Streams [Requisition]s.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,28 @@ package org.wfanet.measurement.kingdom.db.testing

import com.google.common.truth.Truth.assertThat
import com.google.common.truth.extensions.proto.ProtoTruth.assertThat
import java.time.Clock
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.wfanet.measurement.common.identity.ExternalId
import org.wfanet.measurement.common.toInstant
import org.wfanet.measurement.common.toProtoTime
import org.wfanet.measurement.internal.MetricDefinition
import org.wfanet.measurement.internal.SketchMetricDefinition
import org.wfanet.measurement.internal.kingdom.Advertiser
import org.wfanet.measurement.internal.kingdom.Campaign
import org.wfanet.measurement.internal.kingdom.DataProvider
import org.wfanet.measurement.internal.kingdom.Report
import org.wfanet.measurement.internal.kingdom.Report.ReportState
import org.wfanet.measurement.internal.kingdom.ReportConfig
import org.wfanet.measurement.internal.kingdom.ReportConfigSchedule
import org.wfanet.measurement.internal.kingdom.ReportDetails
import org.wfanet.measurement.internal.kingdom.Requisition
import org.wfanet.measurement.internal.kingdom.Requisition.RequisitionState
import org.wfanet.measurement.internal.kingdom.RequisitionDetails.Refusal
import org.wfanet.measurement.internal.kingdom.TimePeriod
import org.wfanet.measurement.kingdom.db.KingdomRelationalDatabase

const val DUCHY_ID = "duchy-1"
Expand All @@ -38,14 +51,18 @@ val METRIC_DEFINITION: MetricDefinition = MetricDefinition.newBuilder().apply {
sketchConfigId = SKETCH_CONFIG_ID
}
}.build()
val REFUSAL: Refusal = Refusal.newBuilder().apply {
justification = Refusal.Justification.COLLECTION_INTERVAL_TOO_DISTANT
message = "Too old"
}.build()

/** Abstract base class for [KingdomRelationalDatabase] tests. */
@RunWith(JUnit4::class)
abstract class AbstractKingdomRelationalDatabaseTest {
/** [KingdomRelationalDatabase] instance. */
abstract val database: KingdomRelationalDatabase

protected suspend fun buildRequisitionWithParents(): Requisition {
protected suspend fun buildRequisitionWithParents(): RequisitionWithParents {
val advertiser = database.createAdvertiser()
val dataProvider = database.createDataProvider()
val campaign =
Expand All @@ -55,7 +72,7 @@ abstract class AbstractKingdomRelationalDatabaseTest {
PROVIDED_CAMPAIGN_ID
)

return Requisition.newBuilder().apply {
val requisition = Requisition.newBuilder().apply {
externalDataProviderId = campaign.externalDataProviderId
externalCampaignId = campaign.externalCampaignId
combinedPublicKeyResourceId = COMBINED_PUBLIC_KEY_RESOURCE_ID
Expand All @@ -67,11 +84,58 @@ abstract class AbstractKingdomRelationalDatabaseTest {
metricDefinition = METRIC_DEFINITION
}
}.build()

return RequisitionWithParents(
advertiser = advertiser,
dataProvider = dataProvider,
campaign = campaign,
requisition = requisition
)
}

protected suspend fun createRequisitionWithParents(): RequisitionWithParents {
val input = buildRequisitionWithParents()
val requisition = database.createRequisition(input.requisition)
return RequisitionWithParents(input.advertiser, input.dataProvider, input.campaign, requisition)
}

protected suspend fun createReportWithParents(
advertiserId: ExternalId,
vararg campaignIds: ExternalId
): Report {
val reportConfig = database.createReportConfig(
ReportConfig.newBuilder().apply {
externalAdvertiserId = advertiserId.value
state = ReportConfig.ReportConfigState.ACTIVE
reportConfigDetailsBuilder.apply {
reportDurationBuilder.apply {
unit = TimePeriod.Unit.DAY
unitValue = 1
}
addMetricDefinitions(METRIC_DEFINITION)
}
}.build(),
campaignIds.asList()
)
val schedule = database.createSchedule(
ReportConfigSchedule.newBuilder().apply {
externalAdvertiserId = reportConfig.externalAdvertiserId
externalReportConfigId = reportConfig.externalReportConfigId
repetitionSpecBuilder.apply {
start = Clock.systemUTC().instant().toProtoTime()
repetitionPeriod = reportConfig.reportConfigDetails.reportDuration
}
}.build()
)
return database.createNextReport(
ExternalId(schedule.externalScheduleId),
COMBINED_PUBLIC_KEY_RESOURCE_ID
)
}

@Test
fun `createRequisition returns new Requisition`() = runBlocking {
val inputRequisition = buildRequisitionWithParents()
val inputRequisition = buildRequisitionWithParents().requisition

val requisition = database.createRequisition(inputRequisition)

Expand All @@ -83,7 +147,7 @@ abstract class AbstractKingdomRelationalDatabaseTest {

@Test
fun `createRequisition returns existing Requisition`() = runBlocking {
val inputRequisition = buildRequisitionWithParents()
val inputRequisition = buildRequisitionWithParents().requisition
val insertedRequisition = database.createRequisition(inputRequisition)

val requisition = database.createRequisition(insertedRequisition)
Expand All @@ -93,7 +157,7 @@ abstract class AbstractKingdomRelationalDatabaseTest {

@Test
fun `getRequisition returns inserted Requisition`() = runBlocking {
val insertedRequisition = database.createRequisition(buildRequisitionWithParents())
val insertedRequisition = createRequisitionWithParents().requisition

val requisition = database.getRequisition(ExternalId(insertedRequisition.externalRequisitionId))

Expand All @@ -102,13 +166,115 @@ abstract class AbstractKingdomRelationalDatabaseTest {

@Test
fun `fulfillRequisition returns fulfilled Requisition`() = runBlocking {
val initialRequisition = database.createRequisition(buildRequisitionWithParents())
val externalRequisitionId = ExternalId(initialRequisition.externalRequisitionId)
val insertedRequisition = createRequisitionWithParents().requisition
val externalRequisitionId = ExternalId(insertedRequisition.externalRequisitionId)

val update = database.fulfillRequisition(externalRequisitionId, DUCHY_ID)

assertThat(update.original).isEqualTo(initialRequisition)
assertThat(update.original).isEqualTo(insertedRequisition)
assertThat(update.current.state).isEqualTo(RequisitionState.FULFILLED)
assertThat(update.current).isEqualTo(database.getRequisition(externalRequisitionId))
}

@Test
fun `fulfillRequisition updates associated Report details`() = runBlocking {
val (_, _, campaign, requisition) = createRequisitionWithParents()
val insertedReport =
createReportWithParents(
ExternalId(campaign.externalAdvertiserId),
ExternalId(campaign.externalCampaignId)
)
val externalReportId = ExternalId(insertedReport.externalReportId)
val externalRequisitionId = ExternalId(requisition.externalRequisitionId)
database.associateRequisitionToReport(externalRequisitionId, externalReportId)

database.fulfillRequisition(externalRequisitionId, DUCHY_ID)

val report = database.getReport(externalReportId)
assertThat(report.reportDetails.requisitionsList).containsExactly(
ReportDetails.ExternalRequisitionKey.newBuilder().also {
it.externalCampaignId = requisition.externalCampaignId
it.externalDataProviderId = requisition.externalDataProviderId
it.externalRequisitionId = requisition.externalRequisitionId
it.duchyId = DUCHY_ID
}.build()
)
assertThat(report.updateTime.toInstant()).isGreaterThan(insertedReport.updateTime.toInstant())
}

@Test
fun `refuseRequisition returns refused Requisition`() = runBlocking {
val insertedRequisition = createRequisitionWithParents().requisition
val externalRequisitionId = ExternalId(insertedRequisition.externalRequisitionId)

val update = database.refuseRequisition(externalRequisitionId, REFUSAL)

assertThat(update.original).isEqualTo(insertedRequisition)
assertThat(update.current.state).isEqualTo(RequisitionState.PERMANENTLY_UNAVAILABLE)
assertThat(update.current.requisitionDetails.refusal).isEqualTo(REFUSAL)
assertThat(update.current).isEqualTo(database.getRequisition(externalRequisitionId))
}

@Test
fun `refuseRequisition is no-op when requisition is fulfilled`() = runBlocking {
val insertedRequisition = createRequisitionWithParents().requisition
val externalRequisitionId = ExternalId(insertedRequisition.externalRequisitionId)

database.fulfillRequisition(externalRequisitionId, DUCHY_ID)
val update = database.refuseRequisition(externalRequisitionId, REFUSAL)

assertThat(update.current).isEqualTo(update.original)
assertThat(update.current).isEqualTo(database.getRequisition(externalRequisitionId))
}

@Test
fun `refuseRequisition marks associated report as failed`() = runBlocking {
val (_, _, campaign, requisition) = createRequisitionWithParents()
val insertedReport =
createReportWithParents(
ExternalId(campaign.externalAdvertiserId),
ExternalId(campaign.externalCampaignId)
)
val externalReportId = ExternalId(insertedReport.externalReportId)
val externalRequisitionId = ExternalId(requisition.externalRequisitionId)
database.associateRequisitionToReport(externalRequisitionId, externalReportId)

database.refuseRequisition(externalRequisitionId, REFUSAL)

val report = database.getReport(externalReportId)
assertThat(report.state).isEqualTo(ReportState.FAILED)
assertThat(report.updateTime.toInstant()).isGreaterThan(insertedReport.updateTime.toInstant())
}

@Test
fun `associateRequisitionToReport links requisition and report`() = runBlocking<Unit> {
val (_, _, campaign, requisition) = createRequisitionWithParents()
val insertedReport =
createReportWithParents(
ExternalId(campaign.externalAdvertiserId),
ExternalId(campaign.externalCampaignId)
)

val externalReportId = ExternalId(insertedReport.externalReportId)
database.associateRequisitionToReport(
ExternalId(requisition.externalRequisitionId),
externalReportId
)

val report = database.getReport(externalReportId)
assertThat(report.reportDetails.requisitionsList).containsExactly(
ReportDetails.ExternalRequisitionKey.newBuilder().apply {
externalCampaignId = requisition.externalCampaignId
externalDataProviderId = requisition.externalDataProviderId
externalRequisitionId = requisition.externalRequisitionId
}.build()
)
}

data class RequisitionWithParents(
val advertiser: Advertiser,
val dataProvider: DataProvider,
val campaign: Campaign,
val requisition: Requisition
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import org.wfanet.measurement.internal.kingdom.ReportConfigSchedule
import org.wfanet.measurement.internal.kingdom.ReportDetails
import org.wfanet.measurement.internal.kingdom.ReportLogEntry
import org.wfanet.measurement.internal.kingdom.Requisition
import org.wfanet.measurement.internal.kingdom.RequisitionDetails
import org.wfanet.measurement.internal.kingdom.RequisitionTemplate
import org.wfanet.measurement.kingdom.db.KingdomRelationalDatabase
import org.wfanet.measurement.kingdom.db.RequisitionUpdate
Expand All @@ -54,6 +55,7 @@ import org.wfanet.measurement.kingdom.deploy.gcloud.spanner.writers.CreateRequis
import org.wfanet.measurement.kingdom.deploy.gcloud.spanner.writers.CreateSchedule
import org.wfanet.measurement.kingdom.deploy.gcloud.spanner.writers.FinishReport
import org.wfanet.measurement.kingdom.deploy.gcloud.spanner.writers.FulfillRequisition
import org.wfanet.measurement.kingdom.deploy.gcloud.spanner.writers.RefuseRequisition
import org.wfanet.measurement.kingdom.deploy.gcloud.spanner.writers.SpannerWriter
import org.wfanet.measurement.kingdom.deploy.gcloud.spanner.writers.UpdateReportState

Expand Down Expand Up @@ -81,6 +83,13 @@ class SpannerKingdomRelationalDatabase(
return FulfillRequisition(externalRequisitionId, duchyId).execute()
}

override suspend fun refuseRequisition(
externalRequisitionId: ExternalId,
refusal: RequisitionDetails.Refusal
): RequisitionUpdate {
return RefuseRequisition(externalRequisitionId, refusal).execute()
}

override fun streamRequisitions(
filter: StreamRequisitionsFilter,
limit: Long
Expand Down
Loading

0 comments on commit 19d5e6b

Please sign in to comment.