text: package com.github.triplet.gradle.androidpublisher.internal
  
  import com.github.triplet.gradle.androidpublisher.EditManager
  import com.github.triplet.gradle.androidpublisher.ReleaseNote
  import com.github.triplet.gradle.androidpublisher.ReleaseStatus
  import com.github.triplet.gradle.androidpublisher.ResolutionStrategy
  import com.google.api.client.googleapis.testing.json.GoogleJsonResponseExceptionFactoryTesting
  import com.google.api.client.json.jackson2.JacksonFactory
  import com.google.api.services.androidpublisher.model.Apk
  import com.google.api.services.androidpublisher.model.AppDetails
  import com.google.api.services.androidpublisher.model.Bundle
  import com.google.api.services.androidpublisher.model.Image
  import com.google.api.services.androidpublisher.model.Listing
  import com.google.api.services.androidpublisher.model.LocalizedText
  import com.google.api.services.androidpublisher.model.Track
  import com.google.api.services.androidpublisher.model.TrackRelease
  import com.google.common.truth.Truth.assertThat
  import org.junit.jupiter.api.Test
  import org.junit.jupiter.api.assertThrows
  import org.mockito.ArgumentMatchers.any
  import org.mockito.ArgumentMatchers.anyInt
  import org.mockito.ArgumentMatchers.eq
  import org.mockito.Mockito.`when`
  import org.mockito.Mockito.mock
  import org.mockito.Mockito.never
  import org.mockito.Mockito.verify
  import java.io.File
  
  class DefaultEditManagerTest {
      private var mockPublisher = mock(InternalPlayPublisher::class.java)
      private var mockTracks = mock(TrackManager::class.java)
      private var edits: EditManager = DefaultEditManager(mockPublisher, mockTracks, "edit-id")
  
      private var mockFile = mock(File::class.java)
  
      @Test
      fun `uploadBundle forwards config to track manager`() {
          `when`(mockPublisher.uploadBundle(any(), any())).thenReturn(Bundle().apply {
              versionCode = 888
          })
  
          edits.uploadBundle(
                  bundleFile = mockFile,
                  mappingFile = null,
                  strategy = ResolutionStrategy.FAIL,
                  didPreviousBuildSkipCommit = false,
                  trackName = "alpha",
                  releaseStatus = ReleaseStatus.COMPLETED,
                  releaseName = "relname",
                  releaseNotes = mapOf("locale" to "notes"),
                  userFraction = .88,
                  updatePriority = 3,
                  retainableArtifacts = listOf(777)
          )
  
          verify(mockTracks).update(TrackManager.UpdateConfig(
                  trackName = "alpha",
                  versionCodes = listOf(888L),
                  didPreviousBuildSkipCommit = false,
                  base = TrackManager.BaseConfig(
                          releaseStatus = ReleaseStatus.COMPLETED,
                          releaseName = "relname",
                          releaseNotes = mapOf("locale" to "notes"),
                          userFraction = .88,
                          updatePriority = 3,
                          retainableArtifacts = listOf(777)
                  )
          ))
      }
  
      @Test
      fun `uploadBundle skips mapping file upload when null`() {
          `when`(mockPublisher.uploadBundle(any(), any())).thenReturn(Bundle().apply {
              versionCode = 888
          })
  
          edits.uploadBundle(
                  bundleFile = mockFile,
                  mappingFile = null,
                  strategy = ResolutionStrategy.FAIL,
                  didPreviousBuildSkipCommit = false,
                  trackName = "alpha",
                  releaseStatus = ReleaseStatus.COMPLETED,
                  releaseName = "relname",
                  releaseNotes = mapOf("locale" to "notes"),
                  userFraction = .88,
                  updatePriority = 3,
                  retainableArtifacts = listOf(777)
          )
  
          verify(mockPublisher, never()).uploadDeobfuscationFile(any(), any(), anyInt())
      }
  
      @Test
      fun `uploadBundle skips mapping file upload when empty`() {
          `when`(mockPublisher.uploadBundle(any(), any())).thenReturn(Bundle().apply {
              versionCode = 888
          })
          `when`(mockFile.length()).thenReturn(0)
  
          edits.uploadBundle(
                  bundleFile = mockFile,
                  mappingFile = mockFile,
                  strategy = ResolutionStrategy.FAIL,
                  didPreviousBuildSkipCommit = false,
                  trackName = "alpha",
                  releaseStatus = ReleaseStatus.COMPLETED,
                  releaseName = "relname",
                  releaseNotes = mapOf("locale" to "notes"),
                  userFraction = .88,
                  updatePriority = 3,
                  retainableArtifacts = listOf(777)
          )
  
          verify(mockPublisher, never()).uploadDeobfuscationFile(any(), any(), anyInt())
      }
  
      @Test
      fun `uploadBundle uploads mapping file when non-empty`() {
          `when`(mockPublisher.uploadBundle(any(), any())).thenReturn(Bundle().apply {
              versionCode = 888
          })
          `when`(mockFile.length()).thenReturn(1)
  
          edits.uploadBundle(
                  bundleFile = mockFile,
                  mappingFile = mockFile,
                  strategy = ResolutionStrategy.FAIL,
                  didPreviousBuildSkipCommit = false,
                  trackName = "alpha",
                  releaseStatus = ReleaseStatus.COMPLETED,
                  releaseName = "relname",
                  releaseNotes = mapOf("locale" to "notes"),
                  userFraction = .88,
                  updatePriority = 3,
                  retainableArtifacts = listOf(777)
          )
  
          verify(mockPublisher).uploadDeobfuscationFile(eq("edit-id"), eq(mockFile), eq(888))
      }
  
      @Test
      fun `uploadBundle fails silently when conflict occurs with ignore strategy`() {
          `when`(mockPublisher.uploadBundle(any(), any())).thenThrow(
                  GoogleJsonResponseExceptionFactoryTesting.newMock(
                          JacksonFactory.getDefaultInstance(), 400, "apkUpgradeVersionConflict"))
  
          edits.uploadBundle(
                  bundleFile = mockFile,
                  mappingFile = mockFile,
                  strategy = ResolutionStrategy.IGNORE,
                  didPreviousBuildSkipCommit = false,
                  trackName = "alpha",
                  releaseStatus = ReleaseStatus.COMPLETED,
                  releaseName = "relname",
                  releaseNotes = mapOf("locale" to "notes"),
                  userFraction = .88,
                  updatePriority = 3,
                  retainableArtifacts = listOf(777)
          )
  
          verify(mockTracks, never()).update(any())
      }
  
      @Test
      fun `uploadBundle fails when conflict occurs with fail strategy`() {
          `when`(mockPublisher.uploadBundle(any(), any())).thenThrow(
                  GoogleJsonResponseExceptionFactoryTesting.newMock(
                          JacksonFactory.getDefaultInstance(), 400, "apkUpgradeVersionConflict"))
  
          assertThrows<IllegalStateException> {
              edits.uploadBundle(
                      bundleFile = mockFile,
                      mappingFile = mockFile,
                      strategy = ResolutionStrategy.FAIL,
                      didPreviousBuildSkipCommit = false,
                      trackName = "alpha",
                      releaseStatus = ReleaseStatus.COMPLETED,
                      releaseName = "relname",
                      releaseNotes = mapOf("locale" to "notes"),
                      userFraction = .88,
                      updatePriority = 3,
                      retainableArtifacts = listOf(777)
              )
          }
      }
  
      @Test
      fun `uploadApk doesn't update tracks`() {
          `when`(mockPublisher.uploadApk(any(), any())).thenReturn(Apk().apply {
              versionCode = 888
          })
  
          val versionCode = edits.uploadApk(
                  apkFile = mockFile,
                  mappingFile = null,
                  strategy = ResolutionStrategy.FAIL,
                  mainObbRetainable = 123,
                  patchObbRetainable = 321
          )
  
          verify(mockTracks, never()).update(any())
          assertThat(versionCode).isEqualTo(888)
      }
  
      @Test
      fun `uploadApk skips mapping file upload when null`() {
          `when`(mockPublisher.uploadApk(any(), any())).thenReturn(Apk().apply {
              versionCode = 888
          })
  
          edits.uploadApk(
                  apkFile = mockFile,
                  mappingFile = mockFile,
                  strategy = ResolutionStrategy.FAIL,
                  mainObbRetainable = 123,
                  patchObbRetainable = 321
          )
  
          verify(mockPublisher, never()).uploadDeobfuscationFile(any(), any(), anyInt())
      }
  
      @Test
      fun `uploadApk skips mapping file upload when empty`() {
          `when`(mockPublisher.uploadApk(any(), any())).thenReturn(Apk().apply {
              versionCode = 888
          })
          `when`(mockFile.length()).thenReturn(0)
  
          edits.uploadApk(
                  apkFile = mockFile,
                  mappingFile = mockFile,
                  strategy = ResolutionStrategy.FAIL,
                  mainObbRetainable = 123,
                  patchObbRetainable = 321
          )
  
          verify(mockPublisher, never()).uploadDeobfuscationFile(any(), any(), anyInt())
      }
  
      @Test
      fun `uploadApk uploads mapping file when non-empty`() {
          `when`(mockPublisher.uploadApk(any(), any())).thenReturn(Apk().apply {
              versionCode = 888
          })
          `when`(mockFile.length()).thenReturn(1)
  
          edits.uploadApk(
                  apkFile = mockFile,
                  mappingFile = mockFile,
                  strategy = ResolutionStrategy.FAIL,
                  mainObbRetainable = 123,
                  patchObbRetainable = 321
          )
  
          verify(mockPublisher).uploadDeobfuscationFile(eq("edit-id"), eq(mockFile), eq(888))
      }
  
      @Test
      fun `uploadApk fails silently when conflict occurs with ignore strategy`() {
          `when`(mockPublisher.uploadApk(any(), any())).thenThrow(
                  GoogleJsonResponseExceptionFactoryTesting.newMock(
                          JacksonFactory.getDefaultInstance(), 400, "apkUpgradeVersionConflict"))
  
          edits.uploadApk(
                  apkFile = mockFile,
                  mappingFile = mockFile,
                  strategy = ResolutionStrategy.IGNORE,
                  mainObbRetainable = 123,
                  patchObbRetainable = 321
          )
  
          verify(mockTracks, never()).update(any())
      }
  
      @Test
      fun `uploadApk fails when conflict occurs with fail strategy`() {
          `when`(mockPublisher.uploadApk(any(), any())).thenThrow(
                  GoogleJsonResponseExceptionFactoryTesting.newMock(
                          JacksonFactory.getDefaultInstance(), 400, "apkUpgradeVersionConflict"))
  
          assertThrows<IllegalStateException> {
              edits.uploadApk(
                      apkFile = mockFile,
                      mappingFile = mockFile,
                      strategy = ResolutionStrategy.FAIL,
                      mainObbRetainable = 123,
                      patchObbRetainable = 321
              )
          }
      }
  
      @Test
      fun `uploadApk attaches OBBs when provided`() {
          `when`(mockPublisher.uploadApk(any(), any())).thenReturn(Apk().apply {
              versionCode = 888
          })
  
          edits.uploadApk(
                  apkFile = mockFile,
                  mappingFile = mockFile,
                  strategy = ResolutionStrategy.FAIL,
                  mainObbRetainable = 123,
                  patchObbRetainable = 321
          )
  
          verify(mockPublisher).attachObb(eq("edit-id"), eq("main"), eq(888), eq(123))
          verify(mockPublisher).attachObb(eq("edit-id"), eq("patch"), eq(888), eq(321))
      }
  
      @Test
      fun `uploadApk ignores OBBs when not provided`() {
          `when`(mockPublisher.uploadApk(any(), any())).thenReturn(Apk().apply {
              versionCode = 888
          })
  
          edits.uploadApk(
                  apkFile = mockFile,
                  mappingFile = mockFile,
                  strategy = ResolutionStrategy.FAIL,
                  mainObbRetainable = null,
                  patchObbRetainable = null
          )
  
          verify(mockPublisher, never()).attachObb(any(), any(), anyInt(), anyInt())
          verify(mockPublisher, never()).attachObb(any(), any(), anyInt(), anyInt())
      }
  
      @Test
      fun `publishApk ignores empty version codes`() {
          edits.publishApk(
                  versionCodes = emptyList(),
                  didPreviousBuildSkipCommit = false,
                  trackName = "alpha",
                  releaseStatus = ReleaseStatus.COMPLETED,
                  releaseName = "relname",
                  releaseNotes = mapOf("locale" to "notes"),
                  userFraction = .88,
                  updatePriority = 3,
                  retainableArtifacts = listOf(777)
          )
  
          verify(mockTracks, never()).update(any())
      }
  
      @Test
      fun `publishApk forwards config to track manager`() {
          edits.publishApk(
                  versionCodes = listOf(888L),
                  didPreviousBuildSkipCommit = false,
                  trackName = "alpha",
                  releaseStatus = ReleaseStatus.COMPLETED,
                  releaseName = "relname",
                  releaseNotes = mapOf("locale" to "notes"),
                  userFraction = .88,
                  updatePriority = 3,
                  retainableArtifacts = listOf(777)
          )
  
          verify(mockTracks).update(TrackManager.UpdateConfig(
                  trackName = "alpha",
                  versionCodes = listOf(888L),
                  didPreviousBuildSkipCommit = false,
                  base = TrackManager.BaseConfig(
                          releaseStatus = ReleaseStatus.COMPLETED,
                          releaseName = "relname",
                          releaseNotes = mapOf("locale" to "notes"),
                          userFraction = .88,
                          updatePriority = 3,
                          retainableArtifacts = listOf(777)
                  )
          ))
      }
  
      @Test
      fun `promoteRelease forwards config to track manager`() {
          edits.promoteRelease(
                  promoteTrackName = "alpha",
                  fromTrackName = "internal",
                  releaseStatus = ReleaseStatus.COMPLETED,
                  releaseName = "relname",
                  releaseNotes = mapOf("locale" to "notes"),
                  userFraction = .88,
                  updatePriority = 3,
                  retainableArtifacts = listOf(777)
          )
  
          verify(mockTracks).promote(TrackManager.PromoteConfig(
                  promoteTrackName = "alpha",
                  fromTrackName = "internal",
                  base = TrackManager.BaseConfig(
                          releaseStatus = ReleaseStatus.COMPLETED,
                          releaseName = "relname",
                          releaseNotes = mapOf("locale" to "notes"),
                          userFraction = .88,
                          updatePriority = 3,
                          retainableArtifacts = listOf(777)
                  )
          ))
      }
  
      @Test
      fun `findMaxAppVersionCode returns 1 on empty tracks`() {
          `when`(mockTracks.findHighestTrack()).thenReturn(null)
  
          val max = edits.findMaxAppVersionCode()
  
          assertThat(max).isEqualTo(1)
      }
  
      @Test
      fun `findMaxAppVersionCode returns 1 on null releases`() {
          `when`(mockTracks.findHighestTrack()).thenReturn(Track())
  
          val max = edits.findMaxAppVersionCode()
  
          assertThat(max).isEqualTo(1)
      }
  
      @Test
      fun `findMaxAppVersionCode succeeds with single track, single release, singe version code`() {
          `when`(mockTracks.findHighestTrack()).thenReturn(Track().apply {
              releases = listOf(
                      TrackRelease().apply {
                          versionCodes = listOf(5)
                      }
              )
          })
  
          val max = edits.findMaxAppVersionCode()
  
          assertThat(max).isEqualTo(5)
      }
  
      @Test
      fun `findMaxAppVersionCode succeeds with single track, single release, multi version code`() {
          `when`(mockTracks.findHighestTrack()).thenReturn(Track().apply {
              releases = listOf(
                      TrackRelease().apply {
                          versionCodes = listOf(5, 4, 8, 7)
                      }
              )
          })
  
          val max = edits.findMaxAppVersionCode()
  
          assertThat(max).isEqualTo(8)
      }
  
      @Test
      fun `findMaxAppVersionCode succeeds with single track, multi release, multi version code`() {
          `when`(mockTracks.findHighestTrack()).thenReturn(Track().apply {
              releases = listOf(
                      TrackRelease().apply {
                          versionCodes = listOf(5, 4, 8, 7)
                      },
                      TrackRelease().apply {
                          versionCodes = listOf(85, 7, 36, 5)
                      }
              )
          })
  
          val max = edits.findMaxAppVersionCode()
  
          assertThat(max).isEqualTo(85)
      }
  
      @Test
      fun `findLeastStableTrackName returns null on null track`() {
          `when`(mockTracks.findHighestTrack()).thenReturn(null)
  
          val highest = edits.findLeastStableTrackName()
  
          assertThat(highest).isNull()
      }
  
      @Test
      fun `findLeastStableTrackName returns track name`() {
          `when`(mockTracks.findHighestTrack()).thenReturn(Track().apply { track = "internal" })
  
          val highest = edits.findLeastStableTrackName()
  
          assertThat(highest).isEqualTo("internal")
      }
  
      @Test
      fun `getReleaseNotes returns data from publisher`() {
          `when`(mockTracks.getReleaseNotes()).thenReturn(mapOf(
                  "alpha" to listOf(LocalizedText().apply {
                      language = "en-US"
                      text = "foobar"
                  }),
                  "production" to listOf(
                          LocalizedText().apply {
                              language = "en-US"
                              text = "prod foobar"
                          },
                          LocalizedText().apply {
                              language = "fr-FR"
                              text = "french foobar"
                          }
                  )
          ))
  
          val notes = edits.getReleaseNotes()
  
          assertThat(notes).containsExactly(
                  ReleaseNote("alpha", "en-US", "foobar"),
                  ReleaseNote("production", "en-US", "prod foobar"),
                  ReleaseNote("production", "fr-FR", "french foobar")
          )
      }
  
      @Test
      fun `getImages returns data from publisher`() {
          `when`(mockPublisher.getImages(any(), any(), any())).thenReturn(listOf(
                  Image().setSha256("a").setUrl("ha"),
                  Image().setSha256("b").setUrl("hb"),
                  Image().setSha256("c").setUrl("hc")
          ))
  
          val result = edits.getImages("en-US", "phoneScreenshots")
  
          assertThat(result.map { it.sha256 }).containsExactly("a", "b", "c")
          assertThat(result.map { it.url }).containsExactly("ha=h16383", "hb=h16383", "hc=h16383")
      }
  
      @Test
      fun `publishAppDetails forwards data to publisher`() {
          edits.publishAppDetails("lang", "email", "phone", "website")
  
          verify(mockPublisher).updateDetails(eq("edit-id"), eq(AppDetails().apply {
              defaultLanguage = "lang"
              contactEmail = "email"
              contactPhone = "phone"
              contactWebsite = "website"
          }))
      }
  
      @Test
      fun `publishListing forwards data to publisher`() {
          edits.publishListing("lang", "title", "short", "full", "url")
  
          verify(mockPublisher).updateListing(eq("edit-id"), eq("lang"), eq(Listing().apply {
              title = "title"
              shortDescription = "short"
              fullDescription = "full"
              video = "url"
          }))
      }
  
      @Test
      fun `publishImages forwards data to publisher`() {
          edits.publishImages("lang", "phoneScreenshots", listOf(mockFile))
  
          verify(mockPublisher).deleteImages(eq("edit-id"), eq("lang"), eq("phoneScreenshots"))
          verify(mockPublisher).uploadImage(
                  eq("edit-id"), eq("lang"), eq("phoneScreenshots"), eq(mockFile))
      }
  }
  