diff --git a/.gitignore b/.gitignore
index 93acc85..fea1822 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,8 +3,9 @@
# Ignore Gradle build output directory
build
+.idea/
.env
*.tar
*.tar.gz
-releases/
\ No newline at end of file
+releases/
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 13566b8..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -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
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
deleted file mode 100644
index b589d56..0000000
--- a/.idea/compiler.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
deleted file mode 100644
index 4979975..0000000
--- a/.idea/gradle.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index 146ab09..0000000
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
deleted file mode 100644
index 206430d..0000000
--- a/.idea/jarRepositories.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
deleted file mode 100644
index e805548..0000000
--- a/.idea/kotlinc.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 2e3f9ea..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 94a25f7..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 0bd20f6..964b583 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -69,3 +69,7 @@ tasks.named("test") {
// Use JUnit Platform for unit tests.
useJUnitPlatform()
}
+
+tasks.withType().configureEach {
+ jvmArgs("--add-opens=java.base/java.util=ALL-UNNAMED")
+}
diff --git a/app/src/main/kotlin/de/rpr/githubreleases/App.kt b/app/src/main/kotlin/de/rpr/githubreleases/App.kt
index 95f9424..7e8a4d4 100644
--- a/app/src/main/kotlin/de/rpr/githubreleases/App.kt
+++ b/app/src/main/kotlin/de/rpr/githubreleases/App.kt
@@ -6,7 +6,8 @@ import java.util.concurrent.TimeUnit
fun main() {
val config = Config()
- val publishers = Publishers(config)
+ val mastodonClientFactory = MastodonClientFactory()
+ val publishers = Publishers(config, mastodonClientFactory)
val releaseRepository = ReleaseRepository()
val app = App(config, releaseRepository, OkHttpClient(), publishers)
app.schedule()
diff --git a/app/src/main/kotlin/de/rpr/githubreleases/Config.kt b/app/src/main/kotlin/de/rpr/githubreleases/Config.kt
index cab906f..383e682 100644
--- a/app/src/main/kotlin/de/rpr/githubreleases/Config.kt
+++ b/app/src/main/kotlin/de/rpr/githubreleases/Config.kt
@@ -49,11 +49,13 @@ class Config {
}
mastodonCredentials = githubRepos.associate {
+ val instanceUrlEnvName = "${it.name.uppercase()}_MASTODON_INSTANCE_URL"
+ val accessTokenEnvName = "${it.name.uppercase()}_MASTODON_ACCESS_TOKEN"
it.name to PublishingCredentials(
- instanceUrlEnvName = "${it.name.uppercase()}_INSTANCE_URL",
- instanceUrl = System.getenv("${it.name.uppercase()}_INSTANCE_URL"),
- accessTokenEnvName = "${it.name.uppercase()}_ACCESS_TOKEN",
- accessToken = System.getenv("${it.name.uppercase()}_ACCESS_TOKEN")
+ instanceUrlEnvName = instanceUrlEnvName,
+ instanceUrl = System.getenv(instanceUrlEnvName),
+ accessTokenEnvName = accessTokenEnvName,
+ accessToken = System.getenv(accessTokenEnvName)
)
}.onEach { (_, credentials) ->
if (!credentials.valid) {
diff --git a/app/src/main/kotlin/de/rpr/githubreleases/MastodonClientFactory.kt b/app/src/main/kotlin/de/rpr/githubreleases/MastodonClientFactory.kt
new file mode 100644
index 0000000..a8561ec
--- /dev/null
+++ b/app/src/main/kotlin/de/rpr/githubreleases/MastodonClientFactory.kt
@@ -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()
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/de/rpr/githubreleases/Publisher.kt b/app/src/main/kotlin/de/rpr/githubreleases/Publisher.kt
index ac73a8c..b22b1da 100644
--- a/app/src/main/kotlin/de/rpr/githubreleases/Publisher.kt
+++ b/app/src/main/kotlin/de/rpr/githubreleases/Publisher.kt
@@ -37,22 +37,29 @@ class Publisher(
}
}
-class Publishers(private val config: Config) : Iterable {
+class Publishers(
+ private val config: Config,
+ private val mastodonClientFactory: MastodonClientFactory,
+) :
+ Iterable {
private val instances: List
init {
val mastodonClients = config.githubRepos.associate {
val publishingCredentials = config.mastodonCredentials[it.name]!!
- it.name to MastodonClient.Builder(publishingCredentials.instanceUrl!!)
- .accessToken(publishingCredentials.accessToken!!).build()
+ it.name to mastodonClientFactory.getClient(
+ publishingCredentials.instanceUrl!!,
+ publishingCredentials.accessToken!!
+ )
}
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 {
return instances.iterator()
}
-}
\ No newline at end of file
+}
+
diff --git a/app/src/main/kotlin/de/rpr/githubreleases/Releases.kt b/app/src/main/kotlin/de/rpr/githubreleases/Releases.kt
index 05c17b6..d7ab887 100644
--- a/app/src/main/kotlin/de/rpr/githubreleases/Releases.kt
+++ b/app/src/main/kotlin/de/rpr/githubreleases/Releases.kt
@@ -13,6 +13,7 @@ data class Release(
"\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.asCollection() = Releases(this)
class Releases(releases: List) : List {
diff --git a/app/src/test/kotlin/de/rpr/githubreleases/ConfigTest.kt b/app/src/test/kotlin/de/rpr/githubreleases/ConfigTest.kt
new file mode 100644
index 0000000..dde1ec7
--- /dev/null
+++ b/app/src/test/kotlin/de/rpr/githubreleases/ConfigTest.kt
@@ -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 {
+ Config()
+ }
+ }
+ }
+ }
+})
\ No newline at end of file
diff --git a/app/src/test/kotlin/de/rpr/githubreleases/PublishersTest.kt b/app/src/test/kotlin/de/rpr/githubreleases/PublishersTest.kt
new file mode 100644
index 0000000..9f3419b
--- /dev/null
+++ b/app/src/test/kotlin/de/rpr/githubreleases/PublishersTest.kt
@@ -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()
+ val mastodonClient = mockk()
+
+ val statuses = mockk(relaxed = true)
+ val mockRequest = mockk>(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(),
+ status = any(),
+ language = any(),
+ visibility = any()
+ )
+ } 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(),
+ status = any(),
+ language = any(),
+ visibility = any()
+ )
+ } returns mockRequest
+
+ publisher.sendReleases(testRelease.copy(id = "v0.0.0").asCollection())
+
+ val statusSlot = slot()
+
+ 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(),
+ status = any(),
+ language = any(),
+ visibility = any()
+ )
+ } returns mockRequest
+
+ publisher.sendReleases(testRelease.asCollection())
+
+ verify(exactly = 0) { mockRequest.execute() }
+ }
+ }
+})
\ No newline at end of file