Tests and refactoring

This commit is contained in:
Ryan Harg 2024-02-12 15:14:59 +01:00
parent 1c59227030
commit 536ac398c4
17 changed files with 216 additions and 97 deletions

3
.gitignore vendored
View file

@ -3,8 +3,9 @@
# Ignore Gradle build output directory # Ignore Gradle build output directory
build build
.idea/
.env .env
*.tar *.tar
*.tar.gz *.tar.gz
releases/ releases/

8
.idea/.gitignore vendored
View file

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
</component>
</project>

View file

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

View file

@ -1,10 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>

View file

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven" />
<option name="name" value="maven" />
<option name="url" value="https://s01.oss.sonatype.org/content/repositories/snapshots/" />
</remote-repository>
</component>
</project>

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.20" />
</component>
</project>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jbr-21" project-jdk-type="JavaSDK" />
</project>

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View file

@ -69,3 +69,7 @@ tasks.named<Test>("test") {
// Use JUnit Platform for unit tests. // Use JUnit Platform for unit tests.
useJUnitPlatform() useJUnitPlatform()
} }
tasks.withType<Test>().configureEach {
jvmArgs("--add-opens=java.base/java.util=ALL-UNNAMED")
}

View file

@ -6,7 +6,8 @@ import java.util.concurrent.TimeUnit
fun main() { fun main() {
val config = Config() val config = Config()
val publishers = Publishers(config) val mastodonClientFactory = MastodonClientFactory()
val publishers = Publishers(config, mastodonClientFactory)
val releaseRepository = ReleaseRepository() val releaseRepository = ReleaseRepository()
val app = App(config, releaseRepository, OkHttpClient(), publishers) val app = App(config, releaseRepository, OkHttpClient(), publishers)
app.schedule() app.schedule()

View file

@ -49,11 +49,13 @@ class Config {
} }
mastodonCredentials = githubRepos.associate { mastodonCredentials = githubRepos.associate {
val instanceUrlEnvName = "${it.name.uppercase()}_MASTODON_INSTANCE_URL"
val accessTokenEnvName = "${it.name.uppercase()}_MASTODON_ACCESS_TOKEN"
it.name to PublishingCredentials( it.name to PublishingCredentials(
instanceUrlEnvName = "${it.name.uppercase()}_INSTANCE_URL", instanceUrlEnvName = instanceUrlEnvName,
instanceUrl = System.getenv("${it.name.uppercase()}_INSTANCE_URL"), instanceUrl = System.getenv(instanceUrlEnvName),
accessTokenEnvName = "${it.name.uppercase()}_ACCESS_TOKEN", accessTokenEnvName = accessTokenEnvName,
accessToken = System.getenv("${it.name.uppercase()}_ACCESS_TOKEN") accessToken = System.getenv(accessTokenEnvName)
) )
}.onEach { (_, credentials) -> }.onEach { (_, credentials) ->
if (!credentials.valid) { if (!credentials.valid) {

View file

@ -0,0 +1,8 @@
package de.rpr.githubreleases
import social.bigbone.MastodonClient
class MastodonClientFactory {
fun getClient(url: String, accessToken: String) = MastodonClient.Builder(url)
.accessToken(accessToken).build()
}

View file

@ -37,22 +37,29 @@ class Publisher(
} }
} }
class Publishers(private val config: Config) : Iterable<Publisher> { class Publishers(
private val config: Config,
private val mastodonClientFactory: MastodonClientFactory,
) :
Iterable<Publisher> {
private val instances: List<Publisher> private val instances: List<Publisher>
init { init {
val mastodonClients = config.githubRepos.associate { val mastodonClients = config.githubRepos.associate {
val publishingCredentials = config.mastodonCredentials[it.name]!! val publishingCredentials = config.mastodonCredentials[it.name]!!
it.name to MastodonClient.Builder(publishingCredentials.instanceUrl!!) it.name to mastodonClientFactory.getClient(
.accessToken(publishingCredentials.accessToken!!).build() publishingCredentials.instanceUrl!!,
publishingCredentials.accessToken!!
)
} }
instances = config.githubRepos.map { Publisher(it.name, mastodonClients[it.name]!!) } instances = config.githubRepos.map { Publisher(it.name, mastodonClients[it.name]!!) }
} }
fun forName(name: String) = instances.first() { it.name == name } fun forName(name: String) = instances.first { it.name == name }
override fun iterator(): Iterator<Publisher> { override fun iterator(): Iterator<Publisher> {
return instances.iterator() return instances.iterator()
} }
} }

View file

@ -13,6 +13,7 @@ data class Release(
"\uD83C\uDF89 Navidrome $title has been published!\n\nRelease notes are available here: $link\n\n#Navidrome" "\uD83C\uDF89 Navidrome $title has been published!\n\nRelease notes are available here: $link\n\n#Navidrome"
} }
fun Release.asCollection() = listOf(this).asCollection()
fun List<Release>.asCollection() = Releases(this) fun List<Release>.asCollection() = Releases(this)
class Releases(releases: List<Release>) : List<Release> { class Releases(releases: List<Release>) : List<Release> {

View file

@ -0,0 +1,49 @@
package de.rpr.githubreleases
import assertk.assertThat
import assertk.assertions.containsExactly
import assertk.assertions.containsOnly
import io.kotest.core.spec.style.DescribeSpec
import io.kotest.extensions.system.withEnvironment
import org.junit.jupiter.api.assertThrows
class ConfigTest : DescribeSpec({
describe("instantiation") {
withEnvironment(
mapOf(
"GITHUB_REPOS" to "https://github.com/navidrome/navidrome/",
"NAVIDROME_MASTODON_INSTANCE_URL" to "example.com",
"NAVIDROME_MASTODON_ACCESS_TOKEN" to "token"
)
) {
it("should populate instances from config") {
val config = Config()
assertThat(config.githubRepos).containsExactly(
GithubRepo("navidrome", "https://github.com/navidrome/navidrome/")
)
assertThat(config.mastodonCredentials).containsOnly(
"navidrome" to Config.PublishingCredentials(
instanceUrl = "example.com",
accessToken = "token",
instanceUrlEnvName = "NAVIDROME_MASTODON_INSTANCE_URL",
accessTokenEnvName = "NAVIDROME_MASTODON_ACCESS_TOKEN"
)
)
}
}
withEnvironment(
mapOf("GITHUB_REPOS" to "https://github.com/navidrome/navidrome/")
) {
it("should throw error if env variables for repo are not present") {
assertThrows<IllegalStateException> {
Config()
}
}
}
}
})

View file

@ -0,0 +1,132 @@
package de.rpr.githubreleases
import assertk.assertThat
import assertk.assertions.isEqualTo
import assertk.assertions.isTrue
import assertk.assertions.prop
import io.kotest.core.spec.style.DescribeSpec
import io.kotest.extensions.system.withEnvironment
import io.mockk.*
import social.bigbone.MastodonClient
import social.bigbone.MastodonRequest
import social.bigbone.api.entity.Status
import social.bigbone.api.entity.data.Visibility
import social.bigbone.api.method.StatusMethods
class PublishersTest : DescribeSpec({
val mastodonClientFactory = mockk<MastodonClientFactory>()
val mastodonClient = mockk<MastodonClient>()
val statuses = mockk<StatusMethods>(relaxed = true)
val mockRequest = mockk<MastodonRequest<Status>>(relaxed = true)
beforeEach {
clearAllMocks()
every { mastodonClient.statuses } returns statuses
every { mastodonClientFactory.getClient(any(), any()) } returns mastodonClient
}
describe("Publishers collection") {
withEnvironment(
mapOf(
"GITHUB_REPOS" to "https://github.com/navidrome/navidrome/",
"NAVIDROME_MASTODON_INSTANCE_URL" to "example.com",
"NAVIDROME_MASTODON_ACCESS_TOKEN" to "token"
)
) {
it("should populate instances from config") {
val publishers = Publishers(Config(), mastodonClientFactory)
assertThat(publishers.iterator().hasNext()).isTrue()
assertThat(publishers.forName("navidrome"))
.prop("name") { it.name }
.isEqualTo("navidrome")
}
}
}
describe("Publisher") {
val publisher = Publisher("example", mastodonClient)
xit("should send with correct visibility") {
every {
statuses.postStatus(
spoilerText = any<String>(),
status = any<String>(),
language = any<String>(),
visibility = any<Visibility>()
)
} returns mockRequest
publisher.sendReleases(Releases(testRelease))
verify {
statuses.postStatus(
status = any(),
mediaIds = any(),
visibility = Visibility.PUBLIC,
inReplyToId = any(),
sensitive = any(),
spoilerText = any(),
language = "en",
addIdempotencyKey = any()
)
}
}
it("should send correct status text") {
every {
statuses.postStatus(
spoilerText = any<String>(),
status = any<String>(),
language = any<String>(),
visibility = any<Visibility>()
)
} returns mockRequest
publisher.sendReleases(testRelease.copy(id = "v0.0.0").asCollection())
val statusSlot = slot<String>()
verify {
statuses.postStatus(
status = capture(statusSlot),
mediaIds = any(),
visibility = any(),
inReplyToId = any(),
sensitive = any(),
spoilerText = any(),
language = any(),
addIdempotencyKey = any()
)
}
assertThat(statusSlot.captured).isEqualTo("\uD83C\uDF89 Navidrome v0.0.0 has been published!\n\nRelease notes are available here: https://example.com\n\n#Navidrome")
}
}
describe("Dry-run mode enabled") {
val publisher = Publisher(name = "example", client = mastodonClient, dryRun = true)
it("should not actually send events") {
every {
statuses.postStatus(
spoilerText = any<String>(),
status = any<String>(),
language = any<String>(),
visibility = any<Visibility>()
)
} returns mockRequest
publisher.sendReleases(testRelease.asCollection())
verify(exactly = 0) { mockRequest.execute() }
}
}
})