From 4d3d5a26a0ea3c43b3ab4c024a6bbd7f06142672 Mon Sep 17 00:00:00 2001 From: Stephen Edwards Date: Wed, 18 Mar 2026 11:38:11 -0400 Subject: [PATCH] Add read-only owners accessor for collectors Amp-Thread-ID: https://ampcode.com/threads/T-019d0187-9cdd-72ae-8781-c8d6d4383941 Co-authored-by: Amp --- .../invert/InvertAllCollectedDataRepo.kt | 28 +++++ .../invert/InvertAllCollectedDataRepoTest.kt | 109 ++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 invert-gradle-plugin/src/test/kotlin/com/squareup/invert/InvertAllCollectedDataRepoTest.kt diff --git a/invert-gradle-plugin/src/main/kotlin/com/squareup/invert/InvertAllCollectedDataRepo.kt b/invert-gradle-plugin/src/main/kotlin/com/squareup/invert/InvertAllCollectedDataRepo.kt index 011946d..3145bbe 100644 --- a/invert-gradle-plugin/src/main/kotlin/com/squareup/invert/InvertAllCollectedDataRepo.kt +++ b/invert-gradle-plugin/src/main/kotlin/com/squareup/invert/InvertAllCollectedDataRepo.kt @@ -9,7 +9,10 @@ import com.squareup.invert.internal.models.InvertCombinedCollectedData import com.squareup.invert.models.ModulePath import com.squareup.invert.models.OwnerInfo import com.squareup.invert.models.Stat +import com.squareup.invert.models.js.AllOwners import com.squareup.invert.models.js.MetadataJsReportModel +import com.squareup.invert.models.js.OwnerDetails +import java.util.Collections /** * A friendly layer on top of the combined data to allow aggregated [Stat]s to be calculated. @@ -36,6 +39,27 @@ class InvertAllCollectedDataRepo( } } + private val ownersSnapshot: AllOwners by lazy { + AllOwners( + ownerToDetails = Collections.unmodifiableMap( + projectMetadata.owners.ownerToDetails.entries.associate { (ownerSlug, ownerDetails) -> + ownerSlug to ownerDetails.asReadOnly() + } + ) + ) + } + + /** + * Returns a read-only snapshot of ownership metadata keyed by owner slug. + * + * This is intended for stat collector aggregation pipelines that need to enrich output with + * owner-level metadata (for example display name, contact, org, team id, or manager details) + * without depending on internal report models. + */ + fun getOwners(): AllOwners { + return ownersSnapshot + } + fun getProject(modulePath: ModulePath): AllCollectedDataForProject? { return AllCollectedDataForProject( collectedDependencies = allCollectedData.collectedDependencies.firstOrNull { it.path == modulePath } @@ -68,4 +92,8 @@ class InvertAllCollectedDataRepo( ), ) } + + private fun OwnerDetails.asReadOnly(): OwnerDetails { + return copy(metadata = Collections.unmodifiableMap(metadata.toMap())) + } } diff --git a/invert-gradle-plugin/src/test/kotlin/com/squareup/invert/InvertAllCollectedDataRepoTest.kt b/invert-gradle-plugin/src/test/kotlin/com/squareup/invert/InvertAllCollectedDataRepoTest.kt new file mode 100644 index 0000000..0e1da78 --- /dev/null +++ b/invert-gradle-plugin/src/test/kotlin/com/squareup/invert/InvertAllCollectedDataRepoTest.kt @@ -0,0 +1,109 @@ +package com.squareup.invert + +import com.squareup.invert.internal.models.InvertCombinedCollectedData +import com.squareup.invert.models.js.AllOwners +import com.squareup.invert.models.js.BuildSystem +import com.squareup.invert.models.js.MetadataJsReportModel +import com.squareup.invert.models.js.OwnerDetails +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue + +class InvertAllCollectedDataRepoTest { + + @Test + fun `getOwners returns owner details when metadata is populated`() { + val owners = AllOwners( + ownerToDetails = mapOf( + "payments" to OwnerDetails( + orgName = "Cash", + metadata = mapOf( + "display_name" to "Payments Platform", + "contact" to "#payments-platform", + "team_id" to "T123", + "core_reviewer" to "jane", + "engineering_manager" to "sam" + ) + ) + ) + ) + val repo = createRepo(owners = owners) + + val result = repo.getOwners() + + assertEquals(1, result.ownerToDetails.size) + val paymentsDetails = result.ownerToDetails.getValue("payments") + assertEquals("Cash", paymentsDetails.orgName) + assertEquals("Payments Platform", paymentsDetails.metadata["display_name"]) + assertEquals("#payments-platform", paymentsDetails.metadata["contact"]) + assertEquals("T123", paymentsDetails.metadata["team_id"]) + assertEquals("jane", paymentsDetails.metadata["core_reviewer"]) + assertEquals("sam", paymentsDetails.metadata["engineering_manager"]) + } + + @Test + fun `getOwners returns empty map when metadata has no owners`() { + val repo = createRepo(owners = AllOwners(ownerToDetails = emptyMap())) + + val result = repo.getOwners() + + assertTrue(result.ownerToDetails.isEmpty()) + } + + @Test + fun `getOwners returns read-only stable snapshot for callers`() { + val repo = createRepo( + owners = AllOwners( + ownerToDetails = mutableMapOf( + "platform" to OwnerDetails( + orgName = "Foundation", + metadata = mutableMapOf("display_name" to "Platform Team") + ) + ) + ) + ) + + val firstRead = repo.getOwners() + + assertFailsWith { + (firstRead.ownerToDetails as MutableMap)["new-owner"] = OwnerDetails() + } + + val firstDetails = firstRead.ownerToDetails.getValue("platform") + assertFailsWith { + (firstDetails.metadata as MutableMap)["contact"] = "#platform" + } + + val secondRead = repo.getOwners() + assertEquals(firstRead, secondRead) + assertEquals("Platform Team", secondRead.ownerToDetails.getValue("platform").metadata["display_name"]) + } + + private fun createRepo(owners: AllOwners): InvertAllCollectedDataRepo { + return InvertAllCollectedDataRepo( + allCollectedData = InvertCombinedCollectedData( + collectedConfigurations = emptySet(), + collectedDependencies = emptySet(), + collectedOwners = emptySet(), + collectedStats = emptySet(), + collectedPlugins = emptySet() + ), + projectMetadata = MetadataJsReportModel( + artifactRepositories = emptyList(), + branchName = "main", + buildSystem = BuildSystem.GRADLE, + currentTime = 0L, + currentTimeFormatted = "0", + latestCommitGitSha = "abc123", + latestCommitTime = 0L, + latestCommitTimeFormatted = "0", + tagName = null, + timezoneId = "UTC", + remoteRepoGit = "git@github.com:example/invert.git", + remoteRepoUrl = "https://github.com/example/invert", + owners = owners + ) + ) + } +}