Skip to content

Commit

Permalink
Add ForgeInstallationRepository implementation
Browse files Browse the repository at this point in the history
- Add Slick-based ForgeInstallationRepository implementation
- Add unit and integration tests for ForgeInstallationRepository implementation
- Update evolutions script to configure new table, indices and foreign key column

fixes #34
  • Loading branch information
tbinna committed Feb 1, 2024
1 parent fcd3011 commit 4ca3f32
Show file tree
Hide file tree
Showing 18 changed files with 720 additions and 96 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.idea
.bsp
target
2 changes: 1 addition & 1 deletion project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ object Dependencies {
}

object Version {
val atlassianConnectPlay = "0.6.0"
val atlassianConnectPlay = "0.6.1"
val playSlick = "5.1.0"
val scalaTestPlusPlay = "5.1.0"
val scalaCheck = "1.14.3"
Expand Down
14 changes: 14 additions & 0 deletions src/it/resources/logback-test.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<configuration>

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

<root level="info">
<appender-ref ref="STDOUT" />
</root>
</configuration>
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package io.toolsplus.atlassian.connect.play.slick

import com.dimafeng.testcontainers.PostgreSQLContainer
import com.dimafeng.testcontainers.scalatest.TestContainerForAll
import org.scalatest.{TestData, TestSuite}
import org.scalatestplus.play.guice.GuiceOneAppPerTest
import org.testcontainers.utility.DockerImageName
import play.api.Application
import play.api.db.DBApi
import play.api.db.evolutions.{ClassLoaderEvolutionsReader, Evolutions}
import play.api.inject.guice.GuiceApplicationBuilder

trait PostgresContainerTest
extends GuiceOneAppPerTest
with TestContainerForAll { self: TestSuite =>

val postgresVersion = "15.5"

override val containerDef: PostgreSQLContainer.Def =
PostgreSQLContainer.Def(
DockerImageName.parse(s"postgres:$postgresVersion"),
databaseName = "intercom",
username = "test",
password = "test",
)

override def newAppForTest(td: TestData): Application = withContainers {
container =>
GuiceApplicationBuilder()
.configure(ContainerDbConfiguration.configuration(container))
.build()
}

def dbApi(implicit app: Application): DBApi =
Application.instanceCache[DBApi].apply(app)

def withEvolutions[T](block: => T): T =
Evolutions.withEvolutions(
dbApi.database("default"),
ClassLoaderEvolutionsReader.forPrefix("evolutions/")) {
block
}

}
Original file line number Diff line number Diff line change
@@ -1,54 +1,20 @@
package io.toolsplus.atlassian.connect.play.slick

import com.dimafeng.testcontainers.PostgreSQLContainer
import com.dimafeng.testcontainers.scalatest.TestContainerForAll
import io.toolsplus.atlassian.connect.play.api.models.DefaultAtlassianHost
import io.toolsplus.atlassian.connect.play.slick.fixtures.AtlassianHostFixture
import org.scalatest.TestData
import org.scalatest.DoNotDiscover
import org.scalatest.concurrent.Eventually
import org.scalatestplus.play.PlaySpec
import org.scalatestplus.play.guice.GuiceOneAppPerTest
import org.testcontainers.utility.DockerImageName
import play.api.Application
import play.api.db.DBApi
import play.api.db.evolutions.{ClassLoaderEvolutionsReader, Evolutions}
import play.api.inject.guice.GuiceApplicationBuilder
import play.api.test.{DefaultAwaitTimeout, FutureAwaits}

@DoNotDiscover
class SlickAtlassianHostRepositoryIt
extends PlaySpec
with GuiceOneAppPerTest
with PostgresContainerTest
with FutureAwaits
with Eventually
with DefaultAwaitTimeout
with TestContainerForAll {

val postgresVersion = "15.5"

override val containerDef: PostgreSQLContainer.Def =
PostgreSQLContainer.Def(
DockerImageName.parse(s"postgres:$postgresVersion"),
databaseName = "intercom",
username = "test",
password = "test",
)

override def newAppForTest(td: TestData): Application = withContainers {
container =>
GuiceApplicationBuilder()
.configure(ContainerDbConfiguration.configuration(container))
.build()
}

def dbApi(implicit app: Application): DBApi =
Application.instanceCache[DBApi].apply(app)

def withEvolutions[T](block: => T): T =
Evolutions.withEvolutions(
dbApi.database("default"),
ClassLoaderEvolutionsReader.forPrefix("evolutions/")) {
block
}
with DefaultAwaitTimeout {

def hostRepo(implicit app: Application): SlickAtlassianHostRepository =
Application.instanceCache[SlickAtlassianHostRepository].apply(app)
Expand Down Expand Up @@ -96,20 +62,6 @@ class SlickAtlassianHostRepositoryIt
} mustBe Some(host)
}
}

"find the inserted host by installation id" in new AtlassianHostFixture {
val connectOnForgeHost: DefaultAtlassianHost = if (host.installationId.isDefined) host else host.copy(installationId = Some("mock-installation-id"))
withEvolutions {
await {
hostRepo.save(connectOnForgeHost)
}

await {
hostRepo.findByInstallationId(connectOnForgeHost.installationId.get)
} mustBe Some(connectOnForgeHost)
}
}

}

"saving the same Atlassian hosts twice" should {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
package io.toolsplus.atlassian.connect.play.slick

import io.toolsplus.atlassian.connect.play.api.models.{
DefaultAtlassianHost,
DefaultForgeInstallation
}
import io.toolsplus.atlassian.connect.play.slick.generators.AtlassianHostGen
import org.scalatest.DoNotDiscover
import org.scalatest.concurrent.Eventually
import org.scalatestplus.play.PlaySpec
import play.api.Application
import play.api.test.{DefaultAwaitTimeout, FutureAwaits}

import scala.collection.immutable
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

@DoNotDiscover
class SlickForgeInstallationRepositoryIt
extends PlaySpec
with PostgresContainerTest
with FutureAwaits
with Eventually
with DefaultAwaitTimeout
with AtlassianHostGen {

val hostA: DefaultAtlassianHost =
atlassianHostGen
.retryUntil(_ => true)
.sample
.get
.copy(installationId = Some("ari-installation-id-a"))
val hostB: DefaultAtlassianHost =
atlassianHostGen
.retryUntil(_ => true)
.sample
.get
.copy(installationId = Some("ari-installation-id-b"))

val fakeInstallationHostA: DefaultForgeInstallation =
DefaultForgeInstallation("fake-installation-id", hostA.clientKey)

val fakeInstallationsHostB: Seq[DefaultForgeInstallation] = Seq(
"ari-installation-id-1",
"ari-installation-id-2",
"ari-installation-id-3",
"ari-installation-id-4",
"ari-installation-id-5"
).map(DefaultForgeInstallation(_, hostB.clientKey))

def hostRepo(implicit app: Application): SlickAtlassianHostRepository =
Application.instanceCache[SlickAtlassianHostRepository].apply(app)

def forgeInstallationRepo(
implicit app: Application): SlickForgeInstallationRepository =
Application.instanceCache[SlickForgeInstallationRepository].apply(app)

"Using a Slick Forge installation repository" when {

"repository is empty" should {

"not find any hosts when fetching all" in {
await {
forgeInstallationRepo.all()
} mustEqual immutable.Seq.empty
}

"return None when trying to find a non existent installation by installation id" in {
await {
forgeInstallationRepo.findByInstallationId("fake-installation-id")
} mustBe None
}

"return empty Seq when trying to find a installations by client key" in {
await {
forgeInstallationRepo.findByClientKey("fake-client-key")
} mustBe immutable.Seq.empty
}

}

"saving a Forge installation to the repository" should {

"successfully save the installation" in {
withEvolutions {
await(hostRepo.save(hostA))
await {
forgeInstallationRepo.save(fakeInstallationHostA)
} mustEqual fakeInstallationHostA

await {
forgeInstallationRepo.all()
} mustEqual Seq(fakeInstallationHostA)
}
}

"find the inserted installation by installation id" in {
withEvolutions {
await(hostRepo.save(hostA))
await {
forgeInstallationRepo.save(fakeInstallationHostA)
}

await {
forgeInstallationRepo.findByInstallationId(
fakeInstallationHostA.installationId)
} mustBe Some(fakeInstallationHostA)
}
}

"find the inserted installation by client key" in {
withEvolutions {
await(hostRepo.save(hostA))
await {
forgeInstallationRepo.save(fakeInstallationHostA)
}

await {
forgeInstallationRepo.findByClientKey(
fakeInstallationHostA.clientKey)
} mustBe Seq(fakeInstallationHostA)
}
}

}

"saving different Forge installation with the same client key" should {

"save all installations" in {
withEvolutions {
await(hostRepo.save(hostB))
await {
Future.sequence(
fakeInstallationsHostB.map(forgeInstallationRepo.save))
}

await {
forgeInstallationRepo.all()
} mustBe fakeInstallationsHostB
}
}

}

"saving the same Forge installation twice" should {

"not duplicate the installation" in {
await(hostRepo.save(hostA))
withEvolutions {
await {
forgeInstallationRepo.save(fakeInstallationHostA)
} mustBe fakeInstallationHostA

await {
forgeInstallationRepo.save(fakeInstallationHostA)
} mustBe fakeInstallationHostA

await {
forgeInstallationRepo.all()
} mustBe Seq(fakeInstallationHostA)
}
}

}

"updating a Forge installation" should {

/**
* This scenario appears if there is site import (migration)
* from on Atlassian instance to another. The installation id
* remains the same but the client key changes.
*/
"successfully store the updated version" in {
withEvolutions {
await(hostRepo.save(hostA))
await(hostRepo.save(hostB))
val updated =
fakeInstallationHostA.copy(clientKey = hostB.clientKey)
await {
forgeInstallationRepo.save(fakeInstallationHostA)
} mustBe fakeInstallationHostA

await {
forgeInstallationRepo.save(updated)
} mustBe updated

await {
forgeInstallationRepo.all()
} mustBe Seq(updated)
}
}

}

"deleting a Forge installation by client key" should {

"successfully delete all installations" in {
withEvolutions {
await(hostRepo.save(hostA))
await(hostRepo.save(hostB))
await {
Future.sequence(
fakeInstallationsHostB.map(forgeInstallationRepo.save))
}

await {
forgeInstallationRepo.save(fakeInstallationHostA)
} mustBe fakeInstallationHostA

await {
forgeInstallationRepo.all()
}.size mustBe (fakeInstallationsHostB ++ Seq(fakeInstallationHostA)).size

await {
forgeInstallationRepo.deleteByClientKey(
fakeInstallationsHostB.head.clientKey)
} mustBe fakeInstallationsHostB.size

await {
forgeInstallationRepo.all()
} mustBe Seq(fakeInstallationHostA)
}
}

}

}

}
Loading

0 comments on commit 4ca3f32

Please sign in to comment.