diff --git a/.github/workflows/pull-request-merge-check.yml b/.github/workflows/pull-request-merge-check.yml index c100e253..32d2bc6c 100644 --- a/.github/workflows/pull-request-merge-check.yml +++ b/.github/workflows/pull-request-merge-check.yml @@ -1,16 +1,14 @@ -name: PullRequest Merge Check - +name: pull-request-merge-check.yml on: pull_request: - paths-ignore: - - 'owl/**' branches: - "develop" types: - - opened # default - - reopened # default - - synchronize # default - - ready_for_review # 必要 + - opened + - reopened + - synchronize + - ready_for_review + concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true @@ -22,10 +20,43 @@ permissions: pull-requests: write jobs: - setup: - name: Setup + change: if: github.event.pull_request.draft == false runs-on: ubuntu-latest + outputs: + core: ${{ steps.filter.outputs.core }} + mastodon: ${{ steps.filter.outputs.mastodon }} + activitypub: ${{ steps.filter.outputs.ap }} + owl: ${{ steps.filter.outputs.owl }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + token: ${{ secrets.PAT }} + + - name: Check Changes + uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + core: + - 'hideout-core/**' + - 'libs.versions.toml' + ap: + - 'hideout-activitypub/**' + - 'libs.versions.toml' + mastodon: + - 'hideout-mastodon/**' + - 'libs.versions.toml' + owl: + - 'owl/**' + - 'libs.versions.toml' + + hideout-core-setup: + needs: + - change + if: github.event.pull_request.draft == false && needs.change.outputs.core == 'true' + runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 @@ -48,11 +79,101 @@ jobs: gradle-home-cache-cleanup: true - name: Build - run: ./gradlew classes --no-daemon + run: ./gradlew :hideout-core:classes --no-daemon - unit-test: - name: Unit Test - needs: [ setup ] + hideout-mastodon-setup: + needs: + - change + if: github.event.pull_request.draft == false && needs.change.outputs.mastodon == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + token: ${{ secrets.PAT }} + + - name: Gradle Wrapper Validation + uses: gradle/actions/wrapper-validation@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + cache-read-only: false + gradle-home-cache-cleanup: true + + - name: Build + run: ./gradlew :hideout-mastodon:classes --no-daemon + + hideout-activitypub-setup: + needs: + - change + if: github.event.pull_request.draft == false && needs.change.outputs.activitypub == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + token: ${{ secrets.PAT }} + + - name: Gradle Wrapper Validation + uses: gradle/actions/wrapper-validation@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + cache-read-only: false + gradle-home-cache-cleanup: true + + - name: Build + run: ./gradlew :hideout-activitypub:classes --no-daemon + + owl-setup: + needs: + - change + if: github.event.pull_request.draft == false && needs.change.outputs.owl == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + token: ${{ secrets.PAT }} + + - name: Gradle Wrapper Validation + uses: gradle/actions/wrapper-validation@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + cache-read-only: false + gradle-home-cache-cleanup: true + + - name: Build + working-directory: owl + run: ./gradlew :classes --no-daemon + + hideout-core-unit-test: + needs: + - hideout-core-setup + - change + if: github.event.pull_request.draft == false && needs.change.outputs.core == 'true' runs-on: ubuntu-latest steps: - name: Checkout @@ -75,39 +196,30 @@ jobs: - name: Unit Test run: ./hideout-core/gradlew :hideout-core:koverXmlReport - - name: Add coverage report to PR - if: always() - id: kover - uses: madrapps/jacoco-report@v1.7.0 - with: - paths: | - ${{ github.workspace }}/hideout-core/build/reports/kover/report.xml - token: ${{ secrets.GITHUB_TOKEN }} - title: Code Coverage - update-comment: true - min-coverage-overall: 50 - min-coverage-changed-files: 80 - - name: JUnit Test Report uses: mikepenz/action-junit-report@v4 with: report_paths: '**/TEST-*.xml' + check_name: 'hideout-core JUnit Test Report' - - name: Verify Coverage - run: ./hideout-core/gradlew :hideout-core:koverVerify + - name: Upload Coverage Report + uses: actions/upload-artifact@v4 + with: + name: 'hideout-core.xml' + path: 'hideout-core/build/reports/kover/hideout-core.xml' - lint: - name: Lint - needs: [ setup ] + + hideout-mastodon-unit-test: + needs: + - hideout-mastodon-setup + - change + if: github.event.pull_request.draft == false && needs.change.outputs.mastodon == 'true' runs-on: ubuntu-latest - permissions: - contents: write steps: - name: Checkout uses: actions/checkout@v4 with: - ref: ${{ github.head_ref }} - token: '${{ secrets.PAT }}' + token: ${{ secrets.PAT }} - name: Set up JDK 21 uses: actions/setup-java@v4 @@ -121,8 +233,166 @@ jobs: cache-read-only: false gradle-home-cache-cleanup: true - - name: Build with Gradle - run: ./gradlew :hideout-core:detektMain :hideout-mastodon:detektMain + - name: Unit Test + run: ./hideout-mastodon/gradlew :hideout-mastodon:koverXmlReport + + - name: JUnit Test Report + uses: mikepenz/action-junit-report@v4 + with: + report_paths: '**/TEST-*.xml' + check_name: 'hideout-mastodon JUnit Test Report' + + - name: Upload Coverage Report + uses: actions/upload-artifact@v4 + with: + name: 'hideout-mastodon.xml' + path: 'hideout-mastodon/build/reports/kover/hideout-mastodon.xml' + + hideout-activitypub-unit-test: + needs: + - hideout-activitypub-setup + - change + if: github.event.pull_request.draft == false && needs.change.outputs.activitypub == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + token: ${{ secrets.PAT }} + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + cache-read-only: false + gradle-home-cache-cleanup: true + + - name: Unit Test + run: ./hideout-activitypub/gradlew :hideout-activitypub:koverXmlReport + + - name: JUnit Test Report + uses: mikepenz/action-junit-report@v4 + with: + report_paths: '**/TEST-*.xml' + check_name: 'hideout-activitypub JUnit Test Report' + + - name: Upload Coverage Report + uses: actions/upload-artifact@v4 + with: + name: 'hideout-activitypub.xml' + path: 'hideout-activitypub/build/reports/kover/hideout-activitypub.xml' + + owl-unit-test: + needs: + - owl-setup + - change + if: github.event.pull_request.draft == false && needs.change.outputs.owl == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + token: ${{ secrets.PAT }} + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + cache-read-only: false + gradle-home-cache-cleanup: true + + - name: Unit Test + working-directory: owl + run: ./gradlew :koverXmlReport --rerun-tasks + + - name: JUnit Test Report + uses: mikepenz/action-junit-report@v4 + with: + report_paths: '**/TEST-*.xml' + check_name: 'owl JUnit Test Report' + + - name: Upload Coverage Report + uses: actions/upload-artifact@v4 + with: + name: 'owl.xml' + path: 'owl/build/reports/kover/owl.xml' + + coverage: + if: always() && (needs.change.outputs.core == 'true' || needs.change.outputs.activitypub == 'true' || needs.change.outputs.mastodon == 'true' || needs.change.outputs.owl == 'true') + needs: + - change + - hideout-core-unit-test + - hideout-mastodon-unit-test + - hideout-activitypub-unit-test + - owl-unit-test + runs-on: ubuntu-latest + steps: + - name: Download Coverage Report + uses: actions/download-artifact@v4 + with: + path: 'hideout-core/build/reports/kover' + + - name: Report Coverage + uses: madrapps/jacoco-report@v1.7.1 + with: + paths: | + ${{ github.workspace }}/hideout-core/build/reports/kover/hideout-core.xml/hideout-core.xml, + ${{ github.workspace }}/hideout-core/build/reports/kover/hideout-mastodon.xml/hideout-mastodon.xml, + ${{ github.workspace }}/hideout-core/build/reports/kover/hideout-activitypub.xml/hideout-activitypub.xml + ${{ github.workspace }}/hideout-core/build/reports/kover/owl.xml/owl.xml + token: ${{ secrets.GITHUB_TOKEN }} + title: Code Coverage + update-comment: true + min-coverage-overall: 50 + min-coverage-changed-files: 80 + + lint: + if: always() && (needs.change.outputs.core == 'true' || needs.change.outputs.activitypub == 'true' || needs.change.outputs.mastodon == 'true' || needs.change.outputs.owl == 'true') + needs: + - change + - hideout-core-setup + - hideout-mastodon-setup + - hideout-activitypub-setup + - owl-setup + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + token: ${{ secrets.PAT }} + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + cache-read-only: false + gradle-home-cache-cleanup: true + + - name: Lint + run: ./gradlew :hideout-core:detektMain :hideout-mastodon:detektMain :hideout-activitypub:detektMain + + - name: owl Lint + if: always() + working-directory: owl + run: ./gradlew :detektMain - name: Auto Commit if: ${{ always() }} diff --git a/.gitignore b/.gitignore index e3b32bdc..99c1c155 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,5 @@ out/ /hideout-mastodon/.kotlin/sessions/ /http-client.private.env.json /logs/ +/hideout-mastodon/logs/ +/hideout-mastodon/files/ diff --git a/README.md b/README.md index 2356ce11..0862a37c 100644 --- a/README.md +++ b/README.md @@ -1,75 +1,3 @@ # Hideout -HideoutはMastodon互換APIを備えたSNSで、ActivityPubに対応し、KotlinとSpring Frameworkを使用して制作されているソフトウェアです。現在は開発中で、SNSとして主要な機能を備えていますが、セキュリティの問題や不安定なテーブル定義などがあるためホストすることはおすすめしません。 - -## 特徴 - -### ActivityPubでつながるネットワーク - -ActivityPubを実装しているためMastodonやMisskey、Pleromaとつながることができます。また、ActivityPub以外の分散型の通信プロトコルを実装することがあるかもしれません。 - -### Mastodon互換API - -OAuth2プロバイダーを含めたほとんどのAPIがMastodonとの互換性を持っているため、既存のMastodon クライアントを使用することができます!また、今後Fedibirdやglitch-soc互換のAPIを実装するかもしれません。 - -## セルフホスト - -> [!CAUTION] -> **免責事項** -> -> 本ソフトウェアを利用して発生したすべての事象に対して開発者は責任を負いません。 -> 本ソフトウェアはApache License 2.0を採用しています。 - -現時点でセルフホストはおすすめしませんが、実験用としてホストすることはできます! - -### 使用技術 - -- **Kotlin** 強力な言語機能でアプリケーションの安全性を高めます。 -- **Spring Framework** (Spring Boot/Spring Security/Spring Web/Spring Data) 豊富な機能と堅牢なライブラリでソフトウェアの基幹部分を担います。 -- **OpenAPI** スキーマファーストのエンドポイント自動生成はAPIの安定性を高めます。 - -### 要件 - -#### 起動 - -- Java 21 -- PostgreSQL 12+ -- MongoDB(必須でない) 4.4.x+ - -#### ビルド - -- Java 21 -- Gradle 8+ - -実験用途として、PostgreSQLはH2DB(バンドル)のPostgreSQL互換モードでも問題ありません。 - -Java 17でもビルド/起動ができますが、サポートしません。 - -MongoDBを使用しない場合、構成で`hideout.use-mongodb`をfalseにする必要があります。 - -### ビルド - -今後のリリースでビルド済みjarなどを使う場合はスキップしてください。 - - -```bash -gradle bootJar -``` - -`build/libs/hideout-x.x.x.jar`が生成されます。 - -### 起動 - -適切に設定した`application-dev.yml`などをクラスパス上に準備します。 - -`dev`は`prod`などに置き換えたり、[複数指定すること](https://spring.pleiades.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties.core.spring.profiles.active)もできます。 - -```bash -java -jar build/libs/hideout-x.x.x.jar --spring.profiles.active=dev -``` - -https://spring.pleiades.io/spring-boot/docs/current/reference/html/getting-started.html#getting-started.first-application.executable-jar.gradle - -### 注意事項 - -本ソフトウェアは開発中です。正常に機能しない場合があります。また、連合先に迷惑をかける事になる可能性があります。DB/設定ファイル/その他リソースなどの利用方法に破壊的な変更が入る可能性があります。 +ActivityPubで繋がれるSNSを目指している \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index a6acf140..ee19d44a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -52,6 +52,7 @@ repositories { dependencies { implementation("dev.usbharu:hideout-core:0.0.1") implementation("dev.usbharu:hideout-mastodon:1.0-SNAPSHOT") + implementation("dev.usbharu:hideout-activitypub:1.0-SNAPSHOT") } tasks.register("run") { diff --git a/hideout-activitypub/build.gradle.kts b/hideout-activitypub/build.gradle.kts index 36ec82b7..219cda28 100644 --- a/hideout-activitypub/build.gradle.kts +++ b/hideout-activitypub/build.gradle.kts @@ -1,5 +1,9 @@ +import kotlinx.kover.gradle.plugin.dsl.CoverageUnit + plugins { - kotlin("jvm") version "1.9.25" + alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.detekt) + alias(libs.plugins.kover) } group = "dev.usbharu" @@ -11,6 +15,7 @@ repositories { dependencies { testImplementation(kotlin("test")) + detektPlugins(libs.detekt.formatting) } tasks.test { @@ -18,4 +23,93 @@ tasks.test { } kotlin { jvmToolchain(21) -} \ No newline at end of file +} + + +configurations { + matching { it.name == "detekt" }.all { + resolutionStrategy.eachDependency { + if (requested.group == "org.jetbrains.kotlin") { + useVersion(io.gitlab.arturbosch.detekt.getSupportedKotlinVersion()) + } + } + } + all { + exclude("org.apache.logging.log4j", "log4j-slf4j2-impl") + } +} + +tasks { + withType { + exclude("**/generated/**") + setSource("src/main/kotlin") + exclude("build/") + configureEach { + exclude("**/org/koin/ksp/generated/**", "**/generated/**") + } + } + withType() { + configureEach { + exclude("**/org/koin/ksp/generated/**", "**/generated/**") + } + } + withType { + useJUnitPlatform() + } +} + + +project.gradle.taskGraph.whenReady { + if (this.hasTask(":koverGenerateArtifact")) { + val task = this.allTasks.find { it.name == "test" } + val verificationTask = task as VerificationTask + verificationTask.ignoreFailures = true + } +} + +detekt { + parallel = true + config.setFrom(files("../detekt.yml")) + buildUponDefaultConfig = true + basePath = "${rootDir.absolutePath}/src/main/kotlin" + autoCorrect = true +} + +kover { + currentProject { + sources { + + + } + } + + reports { + verify { + rule { + bound { + minValue = 50 + coverageUnits = CoverageUnit.INSTRUCTION + } + } + } + total { + xml { + title = "Hideout ActivityPub" + xmlFile = file("$buildDir/reports/kover/hideout-activitypub.xml") + } + } + filters { + excludes { + annotatedBy("org.springframework.context.annotation.Configuration") + annotatedBy("org.springframework.boot.context.properties.ConfigurationProperties") + packages( + "dev.usbharu.hideout.controller.mastodon.generated", + "dev.usbharu.hideout.domain.mastodon.model.generated" + ) + packages("org.springframework") + packages("org.jetbrains") + } + } + + } +} diff --git a/hideout-activitypub/gradlew b/hideout-activitypub/gradlew old mode 100644 new mode 100755 diff --git a/hideout-activitypub/settings.gradle.kts b/hideout-activitypub/settings.gradle.kts index 8f4bdd48..c083363f 100644 --- a/hideout-activitypub/settings.gradle.kts +++ b/hideout-activitypub/settings.gradle.kts @@ -3,3 +3,14 @@ plugins { } rootProject.name = "hideout-activitypub" +dependencyResolutionManagement { + repositories { + mavenCentral() + } + + versionCatalogs { + create("libs") { + from(files("../libs.versions.toml")) + } + } +} \ No newline at end of file diff --git a/hideout-activitypub/src/main/kotlin/Main.kt b/hideout-activitypub/src/main/kotlin/Main.kt index 27f6ee1a..e4395ff8 100644 --- a/hideout-activitypub/src/main/kotlin/Main.kt +++ b/hideout-activitypub/src/main/kotlin/Main.kt @@ -2,4 +2,4 @@ package dev.usbharu fun main() { println("Hello World!") -} \ No newline at end of file +} diff --git a/hideout-core/build.gradle.kts b/hideout-core/build.gradle.kts index a9247b41..b488437b 100644 --- a/hideout-core/build.gradle.kts +++ b/hideout-core/build.gradle.kts @@ -211,12 +211,18 @@ kover { reports { verify { rule { - bound{ + bound { minValue = 50 coverageUnits = CoverageUnit.INSTRUCTION } } } + total { + xml { + title = "Hideout Core" + xmlFile = file("$buildDir/reports/kover/hideout-core.xml") + } + } filters { excludes { annotatedBy("org.springframework.context.annotation.Configuration") @@ -229,6 +235,7 @@ kover { packages("org.jetbrains") } } + } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/GetActorDetailApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/GetActorDetailApplicationService.kt index 5bcf059b..19c8ca54 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/GetActorDetailApplicationService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/GetActorDetailApplicationService.kt @@ -24,6 +24,7 @@ import dev.usbharu.hideout.core.domain.model.actor.ActorId import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.domain.model.media.MediaRepository import dev.usbharu.hideout.core.domain.model.support.acct.Acct +import dev.usbharu.hideout.core.domain.model.support.domain.apHost import dev.usbharu.hideout.core.domain.model.support.principal.Principal import org.slf4j.LoggerFactory import org.springframework.stereotype.Component @@ -45,7 +46,7 @@ class GetActorDetailApplicationService( ?: throw IllegalArgumentException("Actor ${command.id} not found.") } else if (command.actorName != null) { val host = if (command.actorName.host.isEmpty()) { - applicationConfig.url.host + applicationConfig.url.apHost } else { command.actorName.host } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/RegisterLocalActorApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/RegisterLocalActorApplicationService.kt index 27f2eae0..1c1796bd 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/RegisterLocalActorApplicationService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/RegisterLocalActorApplicationService.kt @@ -51,7 +51,7 @@ class RegisterLocalActorApplicationService( if (actorDomainService.usernameAlreadyUse(command.name)) { throw IllegalArgumentException("Username already exists") } - val instance = instanceRepository.findByUrl(applicationConfig.url.toURI()) + val instance = instanceRepository.findByUrl(applicationConfig.url) ?: throw InternalServerException("Local instance not found.") val actor = actorFactoryImpl.createLocal( diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/exception/PermissionDeniedException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/exception/PermissionDeniedException.kt index e59663be..53613621 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/exception/PermissionDeniedException.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/exception/PermissionDeniedException.kt @@ -16,7 +16,10 @@ package dev.usbharu.hideout.core.application.exception +import dev.usbharu.hideout.core.domain.model.support.principal.Principal + class PermissionDeniedException : RuntimeException { + constructor(principal: Principal) : super("Permission Denied $principal") constructor() : super() constructor(message: String?) : super(message) constructor(message: String?, cause: Throwable?) : super(message, cause) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/UserGetFilterApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/UserGetFilterApplicationService.kt index e97209fc..b6682c64 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/UserGetFilterApplicationService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/filter/UserGetFilterApplicationService.kt @@ -34,7 +34,8 @@ class UserGetFilterApplicationService(private val filterRepository: FilterReposi ) { override suspend fun internalExecute(command: GetFilter, principal: LocalUser): Filter { val filter = - filterRepository.findByFilterId(FilterId(command.filterId)) ?: throw IllegalArgumentException("Not Found") + filterRepository.findByFilterId(FilterId(command.filterId)) + ?: throw IllegalArgumentException("Filter ${command.filterId} not found.") if (filter.userDetailId != principal.userDetailId) { throw PermissionDeniedException() } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/instance/GetLocalInstanceApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/instance/GetLocalInstanceApplicationService.kt index dfaf121e..0db792d1 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/instance/GetLocalInstanceApplicationService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/instance/GetLocalInstanceApplicationService.kt @@ -46,7 +46,7 @@ class GetLocalInstanceApplicationService( } val instance = ( - instanceRepository.findByUrl(applicationConfig.url.toURI()) + instanceRepository.findByUrl(applicationConfig.url) ?: throw InternalServerException("Local instance not found.") ) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/instance/InitLocalInstanceApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/instance/InitLocalInstanceApplicationService.kt index 77d2e41b..9a05f9d1 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/instance/InitLocalInstanceApplicationService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/instance/InitLocalInstanceApplicationService.kt @@ -19,6 +19,7 @@ package dev.usbharu.hideout.core.application.instance import dev.usbharu.hideout.core.application.shared.Transaction import dev.usbharu.hideout.core.config.ApplicationConfig import dev.usbharu.hideout.core.domain.model.instance.* +import dev.usbharu.hideout.core.domain.model.support.domain.apHost import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService import org.springframework.boot.context.event.ApplicationReadyEvent import org.springframework.boot.info.BuildProperties @@ -36,15 +37,15 @@ class InitLocalInstanceApplicationService( ) { @EventListener(ApplicationReadyEvent::class) suspend fun init() = transaction.transaction { - val findByUrl = instanceRepository.findByUrl(applicationConfig.url.toURI()) + val findByUrl = instanceRepository.findByUrl(applicationConfig.url) if (findByUrl == null) { val instance = Instance( id = InstanceId(idGenerateService.generateId()), - name = InstanceName(applicationConfig.url.host), + name = InstanceName(applicationConfig.url.apHost), description = InstanceDescription(""), - url = applicationConfig.url.toURI(), - iconUrl = applicationConfig.url.toURI(), + url = applicationConfig.url, + iconUrl = applicationConfig.url, sharedInbox = null, software = InstanceSoftware("hideout"), version = InstanceVersion(buildProperties.version), diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/model/ActorDetail.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/model/ActorDetail.kt index 7cbdd246..834abf77 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/model/ActorDetail.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/model/ActorDetail.kt @@ -18,6 +18,7 @@ package dev.usbharu.hideout.core.application.model import dev.usbharu.hideout.core.domain.model.actor.Actor import dev.usbharu.hideout.core.domain.model.media.Media +import dev.usbharu.hideout.core.domain.model.support.domain.apHost import java.net.URI data class ActorDetail( @@ -41,7 +42,7 @@ data class ActorDetail( id = actor.id.id, name = actor.name.name, screenName = actor.screenName.screenName, - host = actor.url.host, + host = actor.url.apHost, instanceId = actor.instance.instanceId, remoteUrl = actor.url, locked = actor.locked, diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/UserFollowRequestApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/UserFollowRequestApplicationService.kt index 0e31b34b..0b5aaecc 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/UserFollowRequestApplicationService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/relationship/UserFollowRequestApplicationService.kt @@ -24,6 +24,7 @@ import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.domain.model.relationship.Relationship import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser +import dev.usbharu.hideout.core.domain.service.relationship.RelationshipDomainService import org.slf4j.LoggerFactory import org.springframework.stereotype.Service @@ -32,6 +33,7 @@ class UserFollowRequestApplicationService( private val relationshipRepository: RelationshipRepository, transaction: Transaction, private val actorRepository: ActorRepository, + private val relationshipDomainService: RelationshipDomainService ) : LocalUserAbstractApplicationService( transaction, logger @@ -43,11 +45,15 @@ class UserFollowRequestApplicationService( val targetId = ActorId(command.targetActorId) val relationship = relationshipRepository.findByActorIdAndTargetId(actor.id, targetId) ?: Relationship.default( - actor.id, - targetId + actor.id, targetId ) - relationship.followRequest() + val inverseRelationship = + relationshipRepository.findByActorIdAndTargetId(targetId, actor.id) ?: Relationship.default( + targetId, actor.id + ) + + relationshipDomainService.followRequest(relationship, inverseRelationship) relationshipRepository.save(relationship) } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/ApplicationConfig.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/ApplicationConfig.kt index 5cb5941c..8a91aae0 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/ApplicationConfig.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/ApplicationConfig.kt @@ -17,11 +17,11 @@ package dev.usbharu.hideout.core.config import org.springframework.boot.context.properties.ConfigurationProperties -import java.net.URL +import java.net.URI @ConfigurationProperties("hideout") data class ApplicationConfig( - val url: URL, + val url: URI, val private: Boolean = true, val keySize: Int = 2048, ) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/Filter.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/Filter.kt index 43d18485..030c2deb 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/Filter.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/Filter.kt @@ -82,6 +82,16 @@ class Filter( } override fun hashCode(): Int = id.hashCode() + override fun toString(): String { + return "Filter(" + + "id=$id, " + + "userDetailId=$userDetailId, " + + "name=$name, " + + "filterContext=$filterContext, " + + "filterAction=$filterAction, " + + "filterKeywords=$filterKeywords" + + ")" + } companion object { fun isAllow(user: UserDetail, action: Action, resource: Filter): Boolean { diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterKeyword.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterKeyword.kt index fb404f20..ff139522 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterKeyword.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterKeyword.kt @@ -30,7 +30,13 @@ class FilterKeyword( return id == other.id } - override fun hashCode(): Int { - return id.hashCode() + override fun hashCode(): Int = id.hashCode() + + override fun toString(): String { + return "FilterKeyword(" + + "id=$id, " + + "keyword=$keyword, " + + "mode=$mode" + + ")" } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterName.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterName.kt index b88dbf71..64c5dd67 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterName.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterName.kt @@ -29,8 +29,12 @@ class FilterName(name: String) { return name == other.name } - override fun hashCode(): Int { - return name.hashCode() + override fun hashCode(): Int = name.hashCode() + + override fun toString(): String { + return "FilterName(" + + "name='$name'" + + ")" } companion object { diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterResult.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterResult.kt index 7bd074c6..8c590718 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterResult.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterResult.kt @@ -16,4 +16,29 @@ package dev.usbharu.hideout.core.domain.model.filter -class FilterResult(val filter: Filter, val matchedKeyword: String) +class FilterResult(val filter: Filter, val matchedKeyword: String) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as FilterResult + + if (filter != other.filter) return false + if (matchedKeyword != other.matchedKeyword) return false + + return true + } + + override fun hashCode(): Int { + var result = filter.hashCode() + result = 31 * result + matchedKeyword.hashCode() + return result + } + + override fun toString(): String { + return "FilterResult(" + + "filter=$filter, " + + "matchedKeyword='$matchedKeyword'" + + ")" + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilteredPost.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilteredPost.kt index 5d75f380..2b4934c0 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilteredPost.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilteredPost.kt @@ -18,4 +18,29 @@ package dev.usbharu.hideout.core.domain.model.filter import dev.usbharu.hideout.core.domain.model.post.Post -class FilteredPost(val post: Post, val filterResults: List) +class FilteredPost(val post: Post, val filterResults: List) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as FilteredPost + + if (post != other.post) return false + if (filterResults != other.filterResults) return false + + return true + } + + override fun hashCode(): Int { + var result = post.hashCode() + result = 31 * result + filterResults.hashCode() + return result + } + + override fun toString(): String { + return "FilteredPost(" + + "post=$post, " + + "filterResults=$filterResults" + + ")" + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/Relationship.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/Relationship.kt index f1f48851..ebb9753a 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/Relationship.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/Relationship.kt @@ -52,8 +52,8 @@ class Relationship( addDomainEvent(relationshipEventFactory.createEvent(RelationshipEvent.UNFOLLOW_REQUEST)) } - fun block() { - require(following.not()) + private fun block() { + unfollow() blocking = true addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.BLOCK)) } @@ -81,7 +81,7 @@ class Relationship( mutingFollowRequest = false } - fun followRequest() { + private fun followRequest() { require(blocking.not()) followRequesting = true addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.FOLLOW_REQUEST)) @@ -123,6 +123,17 @@ class Relationship( return result } + @Suppress("UnnecessaryAbstractClass") + abstract class InternalRelationshipDomainService { + protected fun block(relationship: Relationship) { + relationship.block() + } + + protected fun followRequest(relationship: Relationship) { + relationship.followRequest() + } + } + companion object { fun default(actorId: ActorId, targetActorId: ActorId): Relationship = Relationship( actorId = actorId, diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/support/domain/Domain.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/support/domain/Domain.kt index b17ab7bd..813be1d6 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/support/domain/Domain.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/support/domain/Domain.kt @@ -16,6 +16,8 @@ package dev.usbharu.hideout.core.domain.model.support.domain +import java.net.URI + @JvmInline value class Domain(val domain: String) { init { @@ -23,6 +25,15 @@ value class Domain(val domain: String) { } companion object { - const val LENGTH = 1000 + const val LENGTH: Int = 1000 + + fun of(uri: URI): Domain = Domain(uri.apHost) } } + +val URI.apHost: String + get() = if (port == -1) { + host + } else { + "$host:$port" + } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actor/RemoteActorCheckDomainService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actor/RemoteActorCheckDomainService.kt index a2f9b6fd..b32446e4 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actor/RemoteActorCheckDomainService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actor/RemoteActorCheckDomainService.kt @@ -18,6 +18,7 @@ package dev.usbharu.hideout.core.domain.service.actor import dev.usbharu.hideout.core.config.ApplicationConfig import dev.usbharu.hideout.core.domain.model.actor.Actor +import dev.usbharu.hideout.core.domain.model.support.domain.apHost import org.springframework.stereotype.Service interface IRemoteActorCheckDomainService { @@ -26,5 +27,5 @@ interface IRemoteActorCheckDomainService { @Service class RemoteActorCheckDomainService(private val applicationConfig: ApplicationConfig) : IRemoteActorCheckDomainService { - override fun isRemoteActor(actor: Actor): Boolean = actor.domain.domain != applicationConfig.url.host + override fun isRemoteActor(actor: Actor): Boolean = actor.domain.domain != applicationConfig.url.apHost } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actor/local/LocalActorDomainServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actor/local/LocalActorDomainServiceImpl.kt index 6f0dd6de..b4cba69d 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actor/local/LocalActorDomainServiceImpl.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actor/local/LocalActorDomainServiceImpl.kt @@ -20,6 +20,7 @@ import dev.usbharu.hideout.core.config.ApplicationConfig import dev.usbharu.hideout.core.domain.model.actor.ActorPrivateKey import dev.usbharu.hideout.core.domain.model.actor.ActorPublicKey import dev.usbharu.hideout.core.domain.model.actor.ActorRepository +import dev.usbharu.hideout.core.domain.model.support.domain.apHost import org.springframework.stereotype.Service import java.security.KeyPairGenerator @@ -29,7 +30,7 @@ class LocalActorDomainServiceImpl( private val applicationConfig: ApplicationConfig, ) : LocalActorDomainService { override suspend fun usernameAlreadyUse(name: String): Boolean = - actorRepository.findByNameAndDomain(name, applicationConfig.url.host) != null + actorRepository.findByNameAndDomain(name, applicationConfig.url.apHost) != null override suspend fun generateKeyPair(): Pair { val keyPairGenerator = KeyPairGenerator.getInstance("RSA") diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/post/DefaultPostReadAccessControl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/post/DefaultPostReadAccessControl.kt index 280bc9bb..e9c1c2ad 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/post/DefaultPostReadAccessControl.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/post/DefaultPostReadAccessControl.kt @@ -77,12 +77,12 @@ class DefaultPostReadAccessControl(private val relationshipRepository: Relations override suspend fun areAllows(postList: List, principal: Principal): List { val actorIds = postList.map { it.actorId } - val relationshipList = + val blockedByList = relationshipRepository.findByActorIdsAndTargetIdAndBlocking(actorIds, principal.actorId, true) .map { it.actorId } - val inverseRelationshipList = + val followingList = relationshipRepository.findByActorIdAndTargetIdsAndFollowing(principal.actorId, actorIds, true) - .map { it.actorId } + .map { it.targetActorId } fun internalAllow(post: Post): Boolean { // ポスト主は無条件で見れる @@ -90,7 +90,7 @@ class DefaultPostReadAccessControl(private val relationshipRepository: Relations return true } - if (relationshipList.contains(post.actorId)) { + if (blockedByList.contains(post.actorId)) { return false } @@ -106,7 +106,7 @@ class DefaultPostReadAccessControl(private val relationshipRepository: Relations return true } - if (post.visibility == Visibility.FOLLOWERS && inverseRelationshipList.contains(principal.actorId)) { + if (post.visibility == Visibility.FOLLOWERS && followingList.contains(post.actorId)) { return true } return false diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/relationship/RelationshipDomainService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/relationship/RelationshipDomainService.kt index b59c0319..7c35d6c0 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/relationship/RelationshipDomainService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/relationship/RelationshipDomainService.kt @@ -17,17 +17,28 @@ package dev.usbharu.hideout.core.domain.service.relationship import dev.usbharu.hideout.core.domain.model.relationship.Relationship +import dev.usbharu.hideout.core.domain.model.relationship.Relationship.InternalRelationshipDomainService import org.springframework.stereotype.Service @Service -class RelationshipDomainService { +class RelationshipDomainService : InternalRelationshipDomainService() { fun block(relationship: Relationship, inverseRelationship: Relationship) { require(relationship != inverseRelationship) require(relationship.actorId == inverseRelationship.targetActorId) require(relationship.targetActorId == inverseRelationship.actorId) - relationship.block() + block(relationship) inverseRelationship.unfollow() inverseRelationship.unfollowRequest() } + + fun followRequest(relationship: Relationship, inverseRelationship: Relationship) { + require(relationship != inverseRelationship) + require(relationship.actorId == inverseRelationship.targetActorId) + require(relationship.targetActorId == inverseRelationship.actorId) + require(inverseRelationship.blocking.not()) + require(relationship.blocking.not()) + + followRequest(relationship) + } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/AbstractRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/AbstractRepository.kt index 9b04ca18..534aa0e6 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/AbstractRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/AbstractRepository.kt @@ -27,7 +27,7 @@ import org.springframework.beans.factory.annotation.Value import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator import java.sql.SQLException -@Suppress("VarCouldBeVal") +@Suppress("VarCouldBeVal", "UnnecessaryAbstractClass") abstract class AbstractRepository(protected val logger: Logger) { private val sqlErrorCodeSQLExceptionTranslator = SQLErrorCodeSQLExceptionTranslator() private val springDataAccessExceptionSQLExceptionTranslator = SpringDataAccessExceptionSQLExceptionTranslator() diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/factory/ActorFactoryImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/factory/ActorFactoryImpl.kt index 9c41792b..1d00d00d 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/factory/ActorFactoryImpl.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/factory/ActorFactoryImpl.kt @@ -20,6 +20,7 @@ import dev.usbharu.hideout.core.config.ApplicationConfig import dev.usbharu.hideout.core.domain.model.actor.* import dev.usbharu.hideout.core.domain.model.instance.InstanceId import dev.usbharu.hideout.core.domain.model.support.domain.Domain +import dev.usbharu.hideout.core.domain.model.support.domain.apHost import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService import org.springframework.stereotype.Component import java.net.URI @@ -40,7 +41,7 @@ class ActorFactoryImpl( return Actor( id = ActorId(idGenerateService.generateId()), name = actorName, - domain = Domain(applicationConfig.url.host), + domain = Domain(applicationConfig.url.apHost), screenName = ActorScreenName(name), description = ActorDescription(""), inbox = URI.create("$userUrl/inbox"), diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsServiceImpl.kt index 713374bd..2fa8ebe2 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsServiceImpl.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsServiceImpl.kt @@ -19,6 +19,7 @@ package dev.usbharu.hideout.core.infrastructure.springframework.oauth2 import dev.usbharu.hideout.core.application.shared.Transaction import dev.usbharu.hideout.core.config.ApplicationConfig import dev.usbharu.hideout.core.domain.model.actor.ActorRepository +import dev.usbharu.hideout.core.domain.model.support.domain.apHost import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository import kotlinx.coroutines.runBlocking import kotlinx.coroutines.slf4j.MDCContext @@ -39,7 +40,7 @@ class UserDetailsServiceImpl( throw UsernameNotFoundException("Username not found") } transaction.transaction { - val actor = actorRepository.findByNameAndDomain(username, applicationConfig.url.host) + val actor = actorRepository.findByNameAndDomain(username, applicationConfig.url.apHost) ?: throw UsernameNotFoundException("$username not found") val userDetail = userDetailRepository.findByActorId(actor.id.id) ?: throw UsernameNotFoundException("${actor.id.id} not found") diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/application/actor/RegisterLocalActorApplicationServiceTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/application/actor/RegisterLocalActorApplicationServiceTest.kt index e0087535..309f41cf 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/application/actor/RegisterLocalActorApplicationServiceTest.kt +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/application/actor/RegisterLocalActorApplicationServiceTest.kt @@ -22,7 +22,7 @@ import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.whenever import utils.TestTransaction -import java.net.URL +import java.net.URI @ExtendWith(MockitoExtension::class) class RegisterLocalActorApplicationServiceTest { @@ -51,7 +51,7 @@ class RegisterLocalActorApplicationServiceTest { val transaction = TestTransaction @Spy - val applicationConfig = ApplicationConfig(URL("http://example.com")) + val applicationConfig = ApplicationConfig(URI.create("http://example.com")) @Spy val idGenerateService = TwitterSnowflakeIdGenerateService diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipTest.kt new file mode 100644 index 00000000..7a9c514b --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipTest.kt @@ -0,0 +1,152 @@ +package dev.usbharu.hideout.core.domain.model.relationship + +import dev.usbharu.hideout.core.domain.event.relationship.RelationshipEvent +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import org.junit.jupiter.api.Test +import utils.AssertDomainEvent.assertContainsEvent +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class RelationshipTest { + @Test + fun unfollow_フォローとフォローリクエストが取り消されUNFOLLOWとUNFOLLOW_REQUESTが発生する() { + val relationship = Relationship( + actorId = ActorId(1), + targetActorId = ActorId(2), + following = true, + blocking = false, + muting = false, + followRequesting = false, + mutingFollowRequest = false + ) + + relationship.unfollow() + + assertFalse(relationship.following) + assertFalse(relationship.followRequesting) + assertContainsEvent(relationship, RelationshipEvent.UNFOLLOW.eventName) + assertContainsEvent(relationship, RelationshipEvent.UNFOLLOW_REQUEST.eventName) + } + + @Test + fun mute_MUTEが発生する() { + val relationship = Relationship( + actorId = ActorId(1), + targetActorId = ActorId(2), + following = true, + blocking = false, + muting = false, + followRequesting = false, + mutingFollowRequest = false + ) + + relationship.mute() + + assertTrue(relationship.muting) + assertContainsEvent(relationship, RelationshipEvent.MUTE.eventName) + } + + @Test + fun unmute_UNMUTEが発生する() { + val relationship = Relationship( + actorId = ActorId(1), + targetActorId = ActorId(2), + following = true, + blocking = false, + muting = true, + followRequesting = false, + mutingFollowRequest = true + ) + + relationship.unmute() + + assertFalse(relationship.muting) + assertContainsEvent(relationship, RelationshipEvent.UNMUTE.eventName) + } + + @Test + fun muteFollowRequest_muteFollowiRequestがtrueになる() { + val relationship = Relationship( + actorId = ActorId(1), + targetActorId = ActorId(2), + following = true, + blocking = false, + muting = false, + followRequesting = true, + mutingFollowRequest = false + ) + + relationship.muteFollowRequest() + + assertTrue(relationship.mutingFollowRequest) + } + + @Test + fun unmuteFollowRequest_muteFollowiRequestがfalseになる() { + val relationship = Relationship( + actorId = ActorId(1), + targetActorId = ActorId(2), + following = true, + blocking = false, + muting = false, + followRequesting = true, + mutingFollowRequest = true + ) + + relationship.unmuteFollowRequest() + + assertFalse(relationship.mutingFollowRequest) + } + + @Test + fun unfollowRequest_followRequestingがfalseになりUNFOLLOW_REQUESTが発生する() { + val relationship = Relationship( + ActorId(1), + targetActorId = ActorId(2), + following = false, + blocking = false, + muting = false, + followRequesting = true, + mutingFollowRequest = false + ) + + relationship.unfollowRequest() + + assertFalse(relationship.followRequesting) + assertContainsEvent(relationship, RelationshipEvent.UNFOLLOW_REQUEST.eventName) + } + + @Test + fun acceptFollowRequest_followingがtrueにfollowRequestingがfalseになりaccept_followが発生する() { + val relationship = Relationship( + actorId = ActorId(1), + targetActorId = ActorId(2), + following = false, + blocking = false, + muting = false, + followRequesting = true, + mutingFollowRequest = true + ) + + relationship.acceptFollowRequest() + assertTrue(relationship.following) + assertContainsEvent(relationship, RelationshipEvent.ACCEPT_FOLLOW.eventName) + } + + @Test + fun rejectFollowRequest_followRequestingがfalseになりREJECT_FOLLOWが発生する() { + val relationship = Relationship( + actorId = ActorId(1), + targetActorId = ActorId(2), + following = false, + blocking = false, + muting = false, + followRequesting = true, + mutingFollowRequest = false + ) + + relationship.rejectFollowRequest() + assertFalse(relationship.followRequesting) + assertContainsEvent(relationship, RelationshipEvent.REJECT_FOLLOW.eventName) + } +} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/service/actor/RemoteActorCheckDomainServiceTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/service/actor/RemoteActorCheckDomainServiceTest.kt index 0b7acd6d..1eb4af84 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/service/actor/RemoteActorCheckDomainServiceTest.kt +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/service/actor/RemoteActorCheckDomainServiceTest.kt @@ -15,7 +15,7 @@ class RemoteActorCheckDomainServiceTest { val remoteActor = RemoteActorCheckDomainService( ApplicationConfig( - URI.create("https://local.example.com").toURL() + URI.create("https://local.example.com") ) ).isRemoteActor( actor @@ -30,7 +30,7 @@ class RemoteActorCheckDomainServiceTest { val localActor = RemoteActorCheckDomainService( ApplicationConfig( - URI.create("https://local.example.com").toURL() + URI.create("https://local.example.com") ) ).isRemoteActor( actor diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/service/actor/local/LocalActorDomainServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/service/actor/local/LocalActorDomainServiceImplTest.kt index a5fcf1a1..4116b0e2 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/service/actor/local/LocalActorDomainServiceImplTest.kt +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/service/actor/local/LocalActorDomainServiceImplTest.kt @@ -15,7 +15,7 @@ import org.mockito.junit.jupiter.MockitoExtension import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.whenever -import java.net.URL +import java.net.URI @ExtendWith(MockitoExtension::class) class LocalActorDomainServiceImplTest { @@ -26,7 +26,7 @@ class LocalActorDomainServiceImplTest { lateinit var actorRepository: ActorRepository @Spy - val applicationConfig = ApplicationConfig(URL("http://example.com")) + val applicationConfig = ApplicationConfig(URI.create("http://example.com")) @Test fun findByNameAndDomainがnullならfalse() = runTest { diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/service/filter/FilterDomainServiceTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/service/filter/FilterDomainServiceTest.kt new file mode 100644 index 00000000..6d43550a --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/service/filter/FilterDomainServiceTest.kt @@ -0,0 +1,86 @@ +package dev.usbharu.hideout.core.domain.service.filter + +import dev.usbharu.hideout.core.domain.model.filter.* +import dev.usbharu.hideout.core.domain.model.post.TestPostFactory +import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +class FilterDomainServiceTest { + @Test + fun apply_filterContextの適用範囲にフィルターが適用される() { + val post = TestPostFactory.create() + + val domainService = FilterDomainService() + val filter = Filter( + FilterId(1), + userDetailId = UserDetailId(1), + FilterName("filter"), + setOf(FilterContext.HOME), + filterAction = FilterAction.HIDE, + setOf(FilterKeyword(FilterKeywordId(1), FilterKeywordKeyword("test"), FilterMode.NONE)) + ) + val apply = domainService.apply(post, FilterContext.HOME, listOf(filter)) + assertEquals(1, apply.filterResults.size) + assertEquals("test", apply.filterResults.first().matchedKeyword) + } + + @Test + fun apply_filterContextに当てはまらないならfilterResultsが空になる() { + val post = TestPostFactory.create() + + val domainService = FilterDomainService() + val filter = Filter( + FilterId(1), + userDetailId = UserDetailId(1), + FilterName("filter"), + setOf(FilterContext.PUBLIC), + filterAction = FilterAction.HIDE, + setOf(FilterKeyword(FilterKeywordId(1), FilterKeywordKeyword("test"), FilterMode.NONE)) + ) + val apply = domainService.apply(post, FilterContext.HOME, listOf(filter)) + assertEquals(0, apply.filterResults.size) + } + + @Test + fun overviewにも適用される() { + val post = TestPostFactory.create(overview = "test") + + val domainService = FilterDomainService() + val filter = Filter( + FilterId(1), + userDetailId = UserDetailId(1), + FilterName("filter"), + setOf(FilterContext.HOME), + filterAction = FilterAction.HIDE, + setOf(FilterKeyword(FilterKeywordId(1), FilterKeywordKeyword("test"), FilterMode.NONE)) + ) + val apply = domainService.apply(post, FilterContext.HOME, listOf(filter)) + assertEquals(2, apply.filterResults.size) + assertEquals("test", apply.filterResults.first().matchedKeyword) + } + + @Test + fun applyAll_filterContextの適用範囲にフィルターが適用される() { + val postList = listOf( + TestPostFactory.create(), + TestPostFactory.create(), + TestPostFactory.create(content = "aaaaaaaaaa"), + TestPostFactory.create(), + TestPostFactory.create() + ) + val filter = Filter( + FilterId(1), + userDetailId = UserDetailId(1), + FilterName("filter"), + setOf(FilterContext.HOME), + filterAction = FilterAction.HIDE, + setOf(FilterKeyword(FilterKeywordId(1), FilterKeywordKeyword("test"), FilterMode.NONE)) + ) + + + val filteredPosts = FilterDomainService().applyAll(postList, FilterContext.HOME, filters = listOf(filter)) + + assertEquals(5, filteredPosts.size) + } +} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/service/post/DefaultPostReadAccessControlTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/service/post/DefaultPostReadAccessControlTest.kt index 5340309f..16736b6c 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/service/post/DefaultPostReadAccessControlTest.kt +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/service/post/DefaultPostReadAccessControlTest.kt @@ -1,6 +1,7 @@ package dev.usbharu.hideout.core.domain.service.post import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.model.post.Post import dev.usbharu.hideout.core.domain.model.post.TestPostFactory import dev.usbharu.hideout.core.domain.model.post.Visibility import dev.usbharu.hideout.core.domain.model.relationship.Relationship @@ -13,12 +14,14 @@ import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertAll import org.junit.jupiter.api.extension.ExtendWith import org.mockito.InjectMocks import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.whenever +import org.mockito.kotlin.* +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals @ExtendWith(MockitoExtension::class) class DefaultPostReadAccessControlTest { @@ -155,4 +158,217 @@ class DefaultPostReadAccessControlTest { assertFalse(actual) } + + @Test + fun ポスト主は無条件で見れる() = runTest { + val actual = service.isAllow( + TestPostFactory.create(actorId = 1, visibility = Visibility.DIRECT), + LocalUser(ActorId(1), UserDetailId(1), Acct("test", "example.com")) + ) + + assertTrue(actual) + } + + @Test + fun areAllows_ポスト主は無条件で見れる() = runTest { + whenever( + relationshipRepository.findByActorIdsAndTargetIdAndBlocking( + any(), + anyValueClass(), + eq(true) + ) + ).doReturn( + emptyList() + ) + whenever( + relationshipRepository.findByActorIdAndTargetIdsAndFollowing( + anyValueClass(), + any(), + eq(true) + ) + ).doReturn( + emptyList() + ) + + val postList = listOf( + TestPostFactory.create(actorId = 1, visibility = Visibility.DIRECT), + TestPostFactory.create(actorId = 1, visibility = Visibility.FOLLOWERS), + TestPostFactory.create(actorId = 1, visibility = Visibility.UNLISTED), + TestPostFactory.create(actorId = 1, visibility = Visibility.PUBLIC), + ) + val actual = service.areAllows(postList, LocalUser(ActorId(1), UserDetailId(1), Acct("test", "example.com"))) + + assertContentEquals(postList, actual) + } + + @Test + fun areFollows_ブロックされていたら見れない() = runTest { + whenever( + relationshipRepository.findByActorIdsAndTargetIdAndBlocking( + any(), + anyValueClass(), + eq(true) + ) + ).doReturn( + listOf(Relationship.default(actorId = ActorId(2), targetActorId = ActorId(1))) + ) + whenever( + relationshipRepository.findByActorIdAndTargetIdsAndFollowing( + anyValueClass(), + any(), + eq(true) + ) + ).doReturn( + emptyList() + ) + + val postList = listOf( + TestPostFactory.create(actorId = 1, visibility = Visibility.DIRECT), + TestPostFactory.create(actorId = 2, visibility = Visibility.FOLLOWERS), + TestPostFactory.create(actorId = 1, visibility = Visibility.UNLISTED), + TestPostFactory.create(actorId = 1, visibility = Visibility.PUBLIC), + ) + val actual = service.areAllows(postList, LocalUser(ActorId(1), UserDetailId(1), Acct("test", "example.com"))) + + assertEquals(3, actual.size) + assertAll(actual.map { { assertEquals(1, it.actorId.id) } }) + } + + + @Test + fun areAllows_PUBLICとUNLISTEDは見れる() = runTest { + whenever( + relationshipRepository.findByActorIdsAndTargetIdAndBlocking( + any(), + anyValueClass(), + eq(true) + ) + ).doReturn( + emptyList() + ) + whenever( + relationshipRepository.findByActorIdAndTargetIdsAndFollowing( + anyValueClass(), + any(), + eq(true) + ) + ).doReturn( + emptyList() + ) + + val postList = listOf( + TestPostFactory.create(actorId = 3, visibility = Visibility.DIRECT), + TestPostFactory.create(actorId = 3, visibility = Visibility.FOLLOWERS), + TestPostFactory.create(actorId = 3, visibility = Visibility.UNLISTED), + TestPostFactory.create(actorId = 3, visibility = Visibility.PUBLIC), + ) + val actual = service.areAllows(postList, LocalUser(ActorId(1), UserDetailId(1), Acct("test", "example.com"))) + + assertEquals(2, actual.size) + kotlin.test.assertTrue(actual.all { it.visibility == Visibility.PUBLIC || it.visibility == Visibility.UNLISTED }) + } + + @Test + fun areAllows_Anonymousは見れない() = runTest { + whenever( + relationshipRepository.findByActorIdsAndTargetIdAndBlocking( + any(), + anyValueClass(), + eq(true) + ) + ).doReturn( + emptyList() + ) + whenever( + relationshipRepository.findByActorIdAndTargetIdsAndFollowing( + anyValueClass(), + any(), + eq(true) + ) + ).doReturn( + emptyList() + ) + + val postList = listOf( + TestPostFactory.create(actorId = 3, visibility = Visibility.DIRECT), + TestPostFactory.create(actorId = 3, visibility = Visibility.FOLLOWERS), + TestPostFactory.create(actorId = 3, visibility = Visibility.UNLISTED), + TestPostFactory.create(actorId = 3, visibility = Visibility.PUBLIC), + ) + val actual = service.areAllows(postList, Anonymous) + + assertEquals(2, actual.size) + kotlin.test.assertTrue(actual.all { it.visibility == Visibility.PUBLIC || it.visibility == Visibility.UNLISTED }) + } + + @Test + fun areAllows_DIRECTはVisibleActorsに入っていたら見れる() = runTest { + whenever( + relationshipRepository.findByActorIdsAndTargetIdAndBlocking( + any(), + anyValueClass(), + eq(true) + ) + ).doReturn( + emptyList() + ) + whenever( + relationshipRepository.findByActorIdAndTargetIdsAndFollowing( + anyValueClass(), + any(), + eq(true) + ) + ).doReturn( + emptyList() + ) + + val postList = listOf( + TestPostFactory.create(id = 1, actorId = 3, visibility = Visibility.DIRECT, visibleActors = listOf(1)), + TestPostFactory.create(id = 2, actorId = 3, visibility = Visibility.DIRECT, visibleActors = listOf(2)), + TestPostFactory.create(id = 3, actorId = 3, visibility = Visibility.DIRECT, visibleActors = listOf(3)), + TestPostFactory.create( + id = 4, + actorId = 3, + visibility = Visibility.DIRECT, + visibleActors = listOf(1, 2, 3, 4) + ), + ) + val actual = service.areAllows(postList, LocalUser(ActorId(1), UserDetailId(1), Acct("test", "example.com"))) + + assertEquals(2, actual.size) + kotlin.test.assertTrue(actual.all { it.id.id == 1L || it.id.id == 4L }) + } + + @Test + fun areAllows_FOLLOWERSはフォローされていたら見れる() = runTest { + whenever( + relationshipRepository.findByActorIdsAndTargetIdAndBlocking( + any(), + anyValueClass(), + eq(true) + ) + ).doReturn( + emptyList() + ) + whenever( + relationshipRepository.findByActorIdAndTargetIdsAndFollowing( + anyValueClass(), + any(), + eq(true) + ) + ).doReturn( + listOf(Relationship.default(actorId = ActorId(1), targetActorId = ActorId(2))) + ) + + val postList = listOf( + TestPostFactory.create(actorId = 3, visibility = Visibility.FOLLOWERS), + TestPostFactory.create(actorId = 2, visibility = Visibility.FOLLOWERS), + TestPostFactory.create(actorId = 3, visibility = Visibility.FOLLOWERS), + TestPostFactory.create(actorId = 3, visibility = Visibility.FOLLOWERS), + ) + val actual = service.areAllows(postList, LocalUser(ActorId(1), UserDetailId(1), Acct("test", "example.com"))) + + assertEquals(1, actual.size) + assertAll(actual.map { { assertEquals(2, it.actorId.id) } }) + } } \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/service/relationship/RelationshipDomainServiceTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/service/relationship/RelationshipDomainServiceTest.kt new file mode 100644 index 00000000..df5886da --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/service/relationship/RelationshipDomainServiceTest.kt @@ -0,0 +1,136 @@ +package dev.usbharu.hideout.core.domain.service.relationship + +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.model.relationship.Relationship +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class RelationshipDomainServiceTest { + @Test + fun block_relationshipとinverseRelationshipが同じ場合失敗() { + val relationship = Relationship.default(ActorId(1), ActorId(2)) + assertThrows { + RelationshipDomainService().block(relationship, relationship) + } + } + + @Test + fun block_relationship_actorIdとinverseRelationshio_targetActorIdが同じ場合失敗() { + val relationship = Relationship.default(ActorId(1), ActorId(2)) + val inverseRelationship = Relationship.default(ActorId(2), ActorId(2)) + assertThrows { + RelationshipDomainService().block(relationship, inverseRelationship) + } + } + + @Test + fun block_relationship_targetActorIdとinverseRelationship_actorIdが同じ場合失敗() { + val relationship = Relationship.default(ActorId(1), ActorId(2)) + val inverseRelationship = Relationship.default(ActorId(1), ActorId(1)) + assertThrows { + RelationshipDomainService().block(relationship, inverseRelationship) + } + } + + @Test + fun block_ブロックされお互いのフォローとフォローリクエストが外れる() { + val relationship = Relationship( + ActorId(1), + ActorId(2), + following = true, + blocking = false, + muting = false, + followRequesting = false, + mutingFollowRequest = false + ) + val inverseRelationship = Relationship( + ActorId(2), + ActorId(1), + following = false, + blocking = false, + followRequesting = true, + mutingFollowRequest = false, + muting = false + ) + + RelationshipDomainService().block(relationship, inverseRelationship) + + assertTrue(relationship.blocking) + assertFalse(relationship.following) + assertFalse(relationship.followRequesting) + assertFalse(inverseRelationship.following) + assertFalse(inverseRelationship.followRequesting) + } + + @Test + fun followRequest_relationshipとinverseRelationshipが同じ場合失敗() { + val relationship = Relationship.default(ActorId(1), ActorId(2)) + assertThrows { + RelationshipDomainService().followRequest(relationship, relationship) + } + } + + @Test + fun followRequest_relationship_actorIdとinverseRelationshio_targetActorIdが同じ場合失敗() { + val relationship = Relationship.default(ActorId(1), ActorId(2)) + val inverseRelationship = Relationship.default(ActorId(2), ActorId(2)) + assertThrows { + RelationshipDomainService().followRequest(relationship, inverseRelationship) + } + } + + @Test + fun followRequest_relationship_targetActorIdとinverseRelationship_actorIdが同じ場合失敗() { + val relationship = Relationship.default(ActorId(1), ActorId(2)) + val inverseRelationship = Relationship.default(ActorId(1), ActorId(1)) + assertThrows { + RelationshipDomainService().followRequest(relationship, inverseRelationship) + } + } + + @Test + fun followRequest_ブロックされてる場合失敗() { + val relationship = Relationship.default(ActorId(1), ActorId(2)) + val inverseRelationship = Relationship( + ActorId(2), + ActorId(1), + following = false, + blocking = true, + muting = false, + followRequesting = false, + mutingFollowRequest = false + ) + assertThrows { + RelationshipDomainService().followRequest(relationship, inverseRelationship) + } + } + + @Test + fun followRequest_ブロックしてる場合は失敗() { + val relationship = Relationship( + ActorId(1), + ActorId(2), + following = false, + blocking = true, + muting = false, + followRequesting = false, + mutingFollowRequest = false + ) + val inverseRelationship = Relationship.default(ActorId(2), ActorId(1)) + assertThrows { + RelationshipDomainService().followRequest(relationship, inverseRelationship) + } + } + + @Test + fun followRequest_followRequestingがtrueになる() { + val relationship = Relationship.default(ActorId(1), ActorId(2)) + val inverseRelationship = Relationship.default(ActorId(2), ActorId(1)) + + RelationshipDomainService().followRequest(relationship, inverseRelationship) + + assertTrue(relationship.followRequesting) + } +} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/service/userdetail/UserDetailDomainServiceTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/service/userdetail/UserDetailDomainServiceTest.kt new file mode 100644 index 00000000..fa75ff66 --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/service/userdetail/UserDetailDomainServiceTest.kt @@ -0,0 +1,28 @@ +package dev.usbharu.hideout.core.domain.service.userdetail + +import dev.usbharu.hideout.core.infrastructure.springframework.SpringSecurityPasswordEncoder +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.InjectMocks +import org.mockito.Spy +import org.mockito.junit.jupiter.MockitoExtension +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder +import kotlin.test.assertNotEquals + +@ExtendWith(MockitoExtension::class) +class UserDetailDomainServiceTest { + + @InjectMocks + lateinit var userDetailDomainService: UserDetailDomainService + + @Spy + val passwordEncoder: PasswordEncoder = SpringSecurityPasswordEncoder(BCryptPasswordEncoder()) + + @Test + fun hash() = runTest { + val hashedPassword = userDetailDomainService.hashPassword("password") + + assertNotEquals("password", hashedPassword.password) + } +} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedRelationshipRepositoryTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedRelationshipRepositoryTest.kt index 8220d6a2..01d0f5ef 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedRelationshipRepositoryTest.kt +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedRelationshipRepositoryTest.kt @@ -464,7 +464,7 @@ class ExposedRelationshipRepositoryTest : AbstractRepositoryTest(Relationships) mutingFollowRequest = false, ) - relationship.block() + relationship.mute() repository.save(relationship) @@ -492,7 +492,7 @@ class ExposedRelationshipRepositoryTest : AbstractRepositoryTest(Relationships) followRequesting = false, mutingFollowRequest = false, ) - relationship.block() + relationship.mute() repository.delete(relationship) diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsServiceImplTest.kt index cf24d329..652de5f0 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsServiceImplTest.kt +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsServiceImplTest.kt @@ -19,7 +19,7 @@ import org.mockito.junit.jupiter.MockitoExtension import org.mockito.kotlin.* import org.springframework.security.core.userdetails.UsernameNotFoundException import utils.TestTransaction -import java.net.URL +import java.net.URI import kotlin.test.assertEquals @ExtendWith(MockitoExtension::class) @@ -34,7 +34,7 @@ class UserDetailsServiceImplTest { lateinit var userDetailRepository: UserDetailRepository @Spy - val applicationConfig = ApplicationConfig(URL("http://example.com")) + val applicationConfig = ApplicationConfig(URI.create("http://example.com")) @Spy val transaction = TestTransaction diff --git a/hideout-mastodon/build.gradle.kts b/hideout-mastodon/build.gradle.kts index 2265937b..37094786 100644 --- a/hideout-mastodon/build.gradle.kts +++ b/hideout-mastodon/build.gradle.kts @@ -1,3 +1,4 @@ +import kotlinx.kover.gradle.plugin.dsl.CoverageUnit import org.openapitools.generator.gradle.plugin.tasks.GenerateTask plugins { @@ -6,6 +7,7 @@ plugins { alias(libs.plugins.spring.boot) alias(libs.plugins.kotlin.spring) alias(libs.plugins.detekt) + alias(libs.plugins.kover) } @@ -60,9 +62,14 @@ dependencies { implementation(libs.bundles.coroutines) testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation("org.springframework.security:spring-security-test") + testImplementation(libs.bundles.spring.boot.oauth2) testImplementation(libs.kotlin.junit) testImplementation(libs.coroutines.test) testImplementation(libs.h2db) + testImplementation(libs.flyway.core) + testImplementation(libs.http.signature) + testRuntimeOnly(libs.flyway.postgresql) } @@ -126,6 +133,53 @@ configurations.matching { it.name == "detekt" }.all { } } +project.gradle.taskGraph.whenReady { + if (this.hasTask(":koverGenerateArtifact")) { + val task = this.allTasks.find { it.name == "test" } + val verificationTask = task as VerificationTask + verificationTask.ignoreFailures = true + } +} + +kover { + currentProject { + sources { + + + } + } + + reports { + verify { + rule { + bound { + minValue = 50 + coverageUnits = CoverageUnit.INSTRUCTION + } + } + } + total { + xml { + title = "Hideout Mastodon" + xmlFile = file("$buildDir/reports/kover/hideout-mastodon.xml") + } + } + filters { + excludes { + annotatedBy("org.springframework.context.annotation.Configuration") + annotatedBy("org.springframework.boot.context.properties.ConfigurationProperties") + packages( + "dev.usbharu.hideout.controller.mastodon.generated", + "dev.usbharu.hideout.domain.mastodon.model.generated" + ) + packages("org.springframework") + packages("org.jetbrains") + } + } + + } +} + tasks.withType { exclude("**/generated/**") doFirst { diff --git a/hideout-mastodon/gradlew b/hideout-mastodon/gradlew old mode 100644 new mode 100755 diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/DeleteFilterV1ApplicationService.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/DeleteFilterV1ApplicationService.kt index 4961d383..53ed42ff 100644 --- a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/DeleteFilterV1ApplicationService.kt +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/DeleteFilterV1ApplicationService.kt @@ -33,7 +33,7 @@ class DeleteFilterV1ApplicationService(private val filterRepository: FilterRepos ) { override suspend fun internalExecute(command: DeleteFilterV1, principal: LocalUser) { val filter = filterRepository.findByFilterKeywordId(FilterKeywordId(command.filterKeywordId)) - ?: throw IllegalArgumentException("Filter ${command.filterKeywordId} not found") + ?: throw IllegalArgumentException("Filter ${command.filterKeywordId} by KeywordId not found") if (principal.userDetailId != filter.userDetailId) { throw PermissionDeniedException() } diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/GetFilterV1ApplicationService.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/GetFilterV1ApplicationService.kt index 6f485361..25e7f658 100644 --- a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/GetFilterV1ApplicationService.kt +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/application/filter/GetFilterV1ApplicationService.kt @@ -39,7 +39,7 @@ class GetFilterV1ApplicationService(private val filterRepository: FilterReposito ?: throw IllegalArgumentException("Filter ${command.filterKeywordId} not found") if (filter.userDetailId != principal.userDetailId) { - throw PermissionDeniedException() + throw PermissionDeniedException(principal) } val filterKeyword = filter.filterKeywords.find { it.id.id == command.filterKeywordId } diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringFilterApi.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringFilterApi.kt index 1734be87..8e92209e 100644 --- a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringFilterApi.kt +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringFilterApi.kt @@ -84,6 +84,7 @@ class SpringFilterApi( account -> FilterContext.ACCOUNT } }.toSet() + val principal = principalContextHolder.getPrincipal() val filter = userRegisterFilterApplicationService.execute( RegisterFilter( v1FilterPostRequest.phrase, @@ -91,12 +92,12 @@ class SpringFilterApi( FilterAction.WARN, setOf(RegisterFilterKeyword(v1FilterPostRequest.phrase, filterMode)) ), - principalContextHolder.getPrincipal() + principal ) return ResponseEntity.ok( getFilterV1ApplicationService.execute( GetFilterV1(filter.filterKeywords.first().id), - principalContextHolder.getPrincipal() + principal ) ) } diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringMediaApi.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringMediaApi.kt index d5bed541..3834f11e 100644 --- a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringMediaApi.kt +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/SpringMediaApi.kt @@ -21,9 +21,12 @@ import dev.usbharu.hideout.core.application.media.UploadMediaApplicationService import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.SpringSecurityOauth2PrincipalContextHolder import dev.usbharu.hideout.mastodon.interfaces.api.generated.MediaApi import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.MediaAttachment +import org.slf4j.LoggerFactory +import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.stereotype.Controller import org.springframework.web.multipart.MultipartFile +import org.springframework.web.server.ResponseStatusException import java.nio.file.Files @Controller @@ -37,6 +40,11 @@ class SpringMediaApi( description: String?, focus: String?, ): ResponseEntity { + if (file.size == 0L) { + logger.warn("File is empty.") + throw ResponseStatusException(HttpStatus.BAD_REQUEST, "File is empty.") + } + val tempFile = Files.createTempFile("hideout-tmp-file", ".tmp") Files.newOutputStream(tempFile).use { outputStream -> @@ -73,4 +81,8 @@ class SpringMediaApi( ) ) } + + companion object { + private val logger = LoggerFactory.getLogger(SpringMediaApi::class.java) + } } diff --git a/hideout-mastodon/src/test/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImplTest.kt b/hideout-mastodon/src/test/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImplTest.kt index da7ef30c..abf5bea5 100644 --- a/hideout-mastodon/src/test/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImplTest.kt +++ b/hideout-mastodon/src/test/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImplTest.kt @@ -6,6 +6,8 @@ import dev.usbharu.hideout.core.domain.model.support.acct.Acct import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId import kotlinx.coroutines.test.runTest +import org.flywaydb.core.Flyway +import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired @@ -30,4 +32,13 @@ class StatusQueryServiceImplTest { assertNull(status) } + + companion object { + @JvmStatic + @AfterAll + fun dropDatabase(@Autowired flyway: Flyway) { + flyway.clean() + flyway.migrate() + } + } } \ No newline at end of file diff --git a/hideout-mastodon/src/test/kotlin/mastodon/account/AccountApiPaginationTest.kt b/hideout-mastodon/src/test/kotlin/mastodon/account/AccountApiPaginationTest.kt new file mode 100644 index 00000000..b2057786 --- /dev/null +++ b/hideout-mastodon/src/test/kotlin/mastodon/account/AccountApiPaginationTest.kt @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package mastodon.account + +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import dev.usbharu.hideout.SpringApplication +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Status +import org.assertj.core.api.Assertions.assertThat +import org.flywaydb.core.Flyway +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors +import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers +import org.springframework.test.context.jdbc.Sql +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.get +import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import org.springframework.transaction.annotation.Transactional +import org.springframework.web.context.WebApplicationContext + +@SpringBootTest(classes = [SpringApplication::class]) +@AutoConfigureMockMvc +@Transactional +@Sql("/sql/actors.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) +@Sql("/sql/accounts/test-accounts-statuses.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) +class AccountApiPaginationTest { + @Autowired + private lateinit var context: WebApplicationContext + + private lateinit var mockMvc: MockMvc + + @Test + fun `apiV1AccountsIdStatusesGet 投稿を取得できる`() { + val content = mockMvc + .get("/api/v1/accounts/1/statuses"){ + with( + SecurityMockMvcRequestPostProcessors.jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + .andExpect { header { string("Link","; rel=\"next\", ; rel=\"prev\"") } } + .andReturn() + .response + .contentAsString + + val value = jacksonObjectMapper().readValue(content, object : TypeReference>() {}) + + assertThat(value.first().id).isEqualTo("100") + assertThat(value.last().id).isEqualTo("81") + assertThat(value).size().isEqualTo(20) + } + + @Test + fun `apiV1AccountsIdStatusesGet 結果が0件のときはLinkヘッダーがない`() { + val content = mockMvc + .get("/api/v1/accounts/1/statuses?min_id=100"){ + with( + SecurityMockMvcRequestPostProcessors.jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .andDo { print() } + .asyncDispatch() + .andExpect { status { isOk() } } + .andExpect { header { doesNotExist("Link") } } + .andReturn() + .response + .contentAsString + + val value = jacksonObjectMapper().readValue(content, object : TypeReference>() {}) + + + assertThat(value).isEmpty() + } + + @Test + fun `apiV1AccountsIdStatusesGet maxIdを指定して取得`() { + val content = mockMvc + .get("/api/v1/accounts/1/statuses?max_id=100"){ + with( + SecurityMockMvcRequestPostProcessors.jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + .andExpect { header { string("Link","; rel=\"next\", ; rel=\"prev\"") } } + .andReturn() + .response + .contentAsString + + val value = jacksonObjectMapper().readValue(content, object : TypeReference>() {}) + + assertThat(value.first().id).isEqualTo("99") + assertThat(value.last().id).isEqualTo("80") + assertThat(value).size().isEqualTo(20) + } + + @Test + fun `apiV1AccountsIdStatusesGet minIdを指定して取得`() { + val content = mockMvc + .get("/api/v1/accounts/1/statuses?min_id=1"){ + with( + SecurityMockMvcRequestPostProcessors.jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + .andExpect { header { string("Link","; rel=\"next\", ; rel=\"prev\"") } } + .andReturn() + .response + .contentAsString + + val value = jacksonObjectMapper().readValue(content, object : TypeReference>() {}) + + assertThat(value.first().id).isEqualTo("21") + assertThat(value.last().id).isEqualTo("2") + assertThat(value).size().isEqualTo(20) + } + + @BeforeEach + fun setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(context) + .apply(SecurityMockMvcConfigurers.springSecurity()) + .build() + } + + companion object { + @JvmStatic + @AfterAll + fun dropDatabase(@Autowired flyway: Flyway) { + flyway.clean() + flyway.migrate() + } + } +} \ No newline at end of file diff --git a/hideout-mastodon/src/test/kotlin/mastodon/account/AccountApiTest.kt b/hideout-mastodon/src/test/kotlin/mastodon/account/AccountApiTest.kt new file mode 100644 index 00000000..c1e5c1be --- /dev/null +++ b/hideout-mastodon/src/test/kotlin/mastodon/account/AccountApiTest.kt @@ -0,0 +1,471 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package mastodon.account + +import dev.usbharu.hideout.SpringApplication +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.model.actor.ActorRepository +import dev.usbharu.hideout.core.infrastructure.exposedrepository.ExposedRelationshipRepository +import kotlinx.coroutines.test.runTest +import org.assertj.core.api.Assertions.assertThat +import org.flywaydb.core.Flyway +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.http.MediaType +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.test.context.support.WithAnonymousUser +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt +import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity +import org.springframework.test.context.jdbc.Sql +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.get +import org.springframework.test.web.servlet.post +import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import org.springframework.transaction.annotation.Transactional +import org.springframework.web.context.WebApplicationContext + +@SpringBootTest(classes = [SpringApplication::class]) +@AutoConfigureMockMvc +@Transactional +@Sql("/sql/actors.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) +class AccountApiTest { + + @Autowired + private lateinit var actorRepository: ActorRepository + + @Autowired + lateinit var relationshipRepository: ExposedRelationshipRepository + + @Autowired + private lateinit var context: WebApplicationContext + + private lateinit var mockMvc: MockMvc + + @BeforeEach + fun setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(context) + .apply(springSecurity()) + .build() + } + + @Test + fun `apiV1AccountsVerifyCredentialsGetにreadでアクセスできる`() { + mockMvc + .get("/api/v1/accounts/verify_credentials") { + with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))) + } + .asyncDispatch() + .andDo { print() } + .andExpect { status { isOk() } } + } + + @Test + fun `apiV1AccountsVerifyCredentialsGetにread_accountsでアクセスできる`() { + mockMvc + .get("/api/v1/accounts/verify_credentials") { + with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:accounts"))) + } + .asyncDispatch() + .andDo { print() } + .andExpect { status { isOk() } } + } + + @Test + @WithAnonymousUser + fun apiV1AccountsVerifyCredentialsGetに匿名でアクセスすると401() { + mockMvc + .get("/api/v1/accounts/verify_credentials") + .andExpect { status { isUnauthorized() } } + } + + @Test + @WithAnonymousUser + fun apiV1AccountsPostに匿名でPOSTしたらアカウントを作成できる() = runTest { + mockMvc + .post("/api/v1/accounts") { + contentType = MediaType.APPLICATION_FORM_URLENCODED + param("username", "api-test-user-1") + param("password", "very-secure-password") + param("email", "test@example.com") + param("agreement", "true") + param("locale", "") + with(csrf()) + } + .asyncDispatch() + .andExpect { status { isFound() } } + + actorRepository.findByNameAndDomain("api-test-user-1", "example.com") + } + + @Test + @WithAnonymousUser + fun apiV1AccountsPostで必須パラメーター以外を省略しても作成できる() = runTest { + mockMvc + .post("/api/v1/accounts") { + contentType = MediaType.APPLICATION_FORM_URLENCODED + param("username", "api-test-user-2") + param("password", "very-secure-password") + with(csrf()) + } + .asyncDispatch() + .andExpect { status { isFound() } } + + actorRepository.findByNameAndDomain("api-test-user-2", "example.com") + } + + @Test + @WithAnonymousUser + fun apiV1AccountsPostでusernameパラメーターを省略したら400() = runTest { + mockMvc + .post("/api/v1/accounts") { + contentType = MediaType.APPLICATION_FORM_URLENCODED + param("password", "api-test-user-3") + with(csrf()) + } + .andDo { print() } + .andExpect { status { isUnprocessableEntity() } } + } + + @Test + @WithAnonymousUser + fun apiV1AccountsPostでpasswordパラメーターを省略したら400() = runTest { + mockMvc + .post("/api/v1/accounts") { + contentType = MediaType.APPLICATION_FORM_URLENCODED + param("username", "api-test-user-4") + with(csrf()) + } + .andExpect { status { isUnprocessableEntity() } } + } + + @Test + @Disabled("JSONでも作れるようにするため") + @WithAnonymousUser + fun apiV1AccountsPostでJSONで作ろうとしても400() { + mockMvc + .post("/api/v1/accounts") { + contentType = MediaType.APPLICATION_JSON + content = """{"username":"api-test-user-5","password":"very-very-secure-password"}""" + with(csrf()) + } + .andExpect { status { isUnsupportedMediaType() } } + } + + @Test + @WithAnonymousUser + fun apiV1AccountsPostにCSRFトークンは必要() { + mockMvc + .post("/api/v1/accounts") { + contentType = MediaType.APPLICATION_FORM_URLENCODED + param("username", "api-test-user-2") + param("password", "very-secure-password") + } + .andExpect { status { isForbidden() } } + } + + @Test + @WithAnonymousUser + fun `apiV1AccountsIdGet 匿名でアカウント情報を取得できる`() { + mockMvc + .get("/api/v1/accounts/1") + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV1AccountsIdFollowPost write_follows権限でPOSTでフォローできる`() { + mockMvc + .post("/api/v1/accounts/2/follow") { + contentType = MediaType.APPLICATION_JSON + with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:follows"))) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV1AccountsIdFollowPost write権限でPOSTでフォローできる`() { + mockMvc + .post("/api/v1/accounts/2/follow") { + contentType = MediaType.APPLICATION_JSON + with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV1AccountsIdFollowPost read権限でだと403`() { + mockMvc + .post("/api/v1/accounts/2/follow") { + contentType = MediaType.APPLICATION_JSON + with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))) + } + .andExpect { status { isForbidden() } } + } + + @Test + @WithAnonymousUser + fun `apiV1AAccountsIdFollowPost 匿名だと401`() { + mockMvc + .post("/api/v1/accounts/2/follow") { + contentType = MediaType.APPLICATION_JSON + with(csrf()) + } + .andExpect { status { isUnauthorized() } } + } + + @Test + @WithAnonymousUser + fun `apiV1AAccountsIdFollowPost 匿名の場合通常csrfトークンは持ってないので403`() { + mockMvc + .post("/api/v1/accounts/2/follow") { + contentType = MediaType.APPLICATION_JSON + } + .andExpect { status { isForbidden() } } + } + + @Test + fun `apiV1AccountsRelationshipsGet 匿名だと401`() { + mockMvc + .get("/api/v1/accounts/relationships") + .andExpect { status { isUnauthorized() } } + } + + @Test + fun `apiV1AccountsRelationshipsGet read_follows権限を持っていたら取得できる`() { + mockMvc + .get("/api/v1/accounts/relationships") { + with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:follows"))) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV1AccountsRelationshipsGet read権限を持っていたら取得できる`() { + mockMvc + .get("/api/v1/accounts/relationships") { + with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV1AccountsRelationshipsGet write権限だと403`() { + mockMvc + .get("/api/v1/accounts/relationships") { + with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))) + } + .andExpect { status { isForbidden() } } + } + + @Test + @Sql("/sql/accounts/apiV1AccountsIdFollowPost フォローできる.sql") + fun `apiV1AccountsIdFollowPost フォローできる`() = runTest { + mockMvc + .post("/api/v1/accounts/3733363/follow") { + contentType = MediaType.APPLICATION_JSON + with(jwt().jwt { it.claim("uid", "37335363") }.authorities(SimpleGrantedAuthority("SCOPE_write"))) + } + .asyncDispatch() + .andExpect { status { isOk() } } + + + val alreadyFollow = relationshipRepository.findByActorIdAndTargetId(ActorId(3733363),ActorId(37335363))?.following + + + assertThat(alreadyFollow).isTrue() + } + + @Test + fun `apiV1AccountsIdMutePost write権限でミュートできる`() { + mockMvc + .post("/api/v1/accounts/2/mute") { + contentType = MediaType.APPLICATION_JSON + with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV1AccountsIdMutePost write_mutes権限でミュートできる`() { + mockMvc + .post("/api/v1/accounts/2/mute") { + contentType = MediaType.APPLICATION_JSON + with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:mutes"))) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV1AccountsIdMutePost read権限だと403`() = runTest { + mockMvc + .post("/api/v1/accounts/2/mute") { + contentType = MediaType.APPLICATION_JSON + with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))) + } + .andExpect { status { isForbidden() } } + } + + @Test + @WithAnonymousUser + fun `apiV1AccountsIdMutePost 匿名だと401`() = runTest { + mockMvc + .post("/api/v1/accounts/2/mute") { + contentType = MediaType.APPLICATION_JSON + with(csrf()) + } + .andExpect { status { isUnauthorized() } } + } + + @Test + @WithAnonymousUser + fun `apiV1AccountsIdMutePost csrfトークンがないと403`() = runTest { + mockMvc + .post("/api/v1/accounts/2/mute") { + contentType = MediaType.APPLICATION_JSON + } + .andExpect { status { isForbidden() } } + } + + @Test + fun `apiV1AccountsIdUnmutePost write権限でアンミュートできる`() { + mockMvc + .post("/api/v1/accounts/2/unmute") { + contentType = MediaType.APPLICATION_JSON + with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV1AccountsIdUnmutePost write_mutes権限でアンミュートできる`() { + mockMvc + .post("/api/v1/accounts/2/unmute") { + contentType = MediaType.APPLICATION_JSON + with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:mutes"))) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV1AccountsIdUnmutePost read権限だと403`() = runTest { + mockMvc + .post("/api/v1/accounts/2/unmute") { + contentType = MediaType.APPLICATION_JSON + with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))) + } + .andExpect { status { isForbidden() } } + } + + @Test + @WithAnonymousUser + fun `apiV1AccountsIdUnmutePost 匿名だと401`() = runTest { + mockMvc + .post("/api/v1/accounts/2/unmute") { + contentType = MediaType.APPLICATION_JSON + with(csrf()) + } + .andExpect { status { isUnauthorized() } } + } + + @Test + @WithAnonymousUser + fun `apiV1AccountsIdUnmutePost csrfトークンがないと403`() = runTest { + mockMvc + .post("/api/v1/accounts/2/unmute") { + contentType = MediaType.APPLICATION_JSON + } + .andExpect { status { isForbidden() } } + } + + @Test + fun `apiV1MutesGet read権限でミュートしているアカウント一覧を取得できる`() { + mockMvc + .get("/api/v1/mutes") { + with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV1MutesGet read_mutes権限でミュートしているアカウント一覧を取得できる`() { + mockMvc + .get("/api/v1/mutes") { + with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:mutes"))) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV1MutesGet write権限だと403`() { + mockMvc + .get("/api/v1/mutes") { + with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))) + } + .andExpect { status { isForbidden() } } + } + + @Test + @WithAnonymousUser + fun `apiV1MutesGet 匿名だと401`() { + mockMvc + .get("/api/v1/mutes") + .andExpect { status { isUnauthorized() } } + } + + @Test + fun `apiV1AccountsIdStatusesGet read権限で取得できる`() { + mockMvc + .get("/api/v1/accounts/1/statuses") + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + @WithAnonymousUser + fun `apiV1AccountsIdStatusesGet 匿名でもpublic投稿を取得できる`() { + mockMvc + .get("/api/v1/accounts/1/statuses") + .asyncDispatch() + .andExpect { status { isOk() } } + } + + companion object { + @JvmStatic + @AfterAll + fun dropDatabase(@Autowired flyway: Flyway) { + flyway.clean() + flyway.migrate() + } + } +} diff --git a/hideout-mastodon/src/test/kotlin/mastodon/apps/AppTest.kt b/hideout-mastodon/src/test/kotlin/mastodon/apps/AppTest.kt new file mode 100644 index 00000000..1b80163c --- /dev/null +++ b/hideout-mastodon/src/test/kotlin/mastodon/apps/AppTest.kt @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package mastodon.apps + +import dev.usbharu.hideout.SpringApplication +import org.assertj.core.api.Assertions.assertThat +import org.flywaydb.core.Flyway +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.http.MediaType +import org.springframework.jdbc.core.JdbcOperations +import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository +import org.springframework.security.test.context.support.WithAnonymousUser +import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.post +import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import org.springframework.transaction.annotation.Transactional +import org.springframework.web.context.WebApplicationContext +import util.objectMapper +import kotlin.test.assertNotNull + +@SpringBootTest(classes = [SpringApplication::class]) +@AutoConfigureMockMvc +@Transactional +class AppTest { + + @Autowired + private lateinit var context: WebApplicationContext + + private lateinit var mockMvc: MockMvc + + @Autowired + lateinit var jdbcOperations: JdbcOperations + + + val registeredClientRepository: JdbcRegisteredClientRepository by lazy { + JdbcRegisteredClientRepository( + jdbcOperations + ) + } + + @BeforeEach + fun setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(context) + .apply(SecurityMockMvcConfigurers.springSecurity()) + .build() + } + + @Test + @WithAnonymousUser + fun apiV1AppsPostにformで匿名でappを作成できる() { + val contentAsString = mockMvc + .post("/api/v1/apps") { + contentType = MediaType.APPLICATION_FORM_URLENCODED + param("client_name", "test-client") + param("redirect_uris", "https://example.com") + param("scopes", "write read") + param("website", "https://example.com") + } + .asyncDispatch() + .andExpect { status { isOk() } } + .andReturn() + .response + .contentAsString + + val clientId = objectMapper().readTree(contentAsString)["client_id"].asText() + + + val registeredClient = registeredClientRepository.findByClientId(clientId) + + assertNotNull(registeredClient) + assertThat(registeredClient.clientName).isEqualTo("test-client") + assertThat(registeredClient.redirectUris.joinToString(",")).isEqualTo("https://example.com") + assertThat(registeredClient.scopes.joinToString(",")).isEqualTo("read,write") + } + + @Test + @WithAnonymousUser + fun apiV1AppsPostにjsonで匿名でappを作成できる() { + val contentAsString = mockMvc + .post("/api/v1/apps") { + contentType = MediaType.APPLICATION_JSON + content = """{ + "client_name": "test-client-2", + "redirect_uris": "https://example.com", + "scopes": "write read", + "website": "https;//example.com" +}""" + } + .asyncDispatch() + .andExpect { status { isOk() } } + .andReturn() + .response + .contentAsString + + val clientId = objectMapper().readTree(contentAsString)["client_id"].asText() + + + val registeredClient = registeredClientRepository.findByClientId(clientId) + + assertNotNull(registeredClient) + assertThat(registeredClient.clientName).isEqualTo("test-client-2") + assertThat(registeredClient.redirectUris.joinToString(",")).isEqualTo("https://example.com") + assertThat(registeredClient.scopes.joinToString(",")).isEqualTo("read,write") + } + + companion object { + @JvmStatic + @AfterAll + fun dropDatabase(@Autowired flyway: Flyway) { + flyway.clean() + flyway.migrate() + } + } +} diff --git a/hideout-mastodon/src/test/kotlin/mastodon/filter/FilterTest.kt b/hideout-mastodon/src/test/kotlin/mastodon/filter/FilterTest.kt new file mode 100644 index 00000000..8052c258 --- /dev/null +++ b/hideout-mastodon/src/test/kotlin/mastodon/filter/FilterTest.kt @@ -0,0 +1,711 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package mastodon.filter + +import dev.usbharu.hideout.SpringApplication +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.FilterKeywordsPostRequest +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.FilterPostRequest +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.FilterPostRequestKeyword +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.V1FilterPostRequest +import kotlinx.coroutines.test.runTest +import org.flywaydb.core.Flyway +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.http.MediaType +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt +import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers +import org.springframework.test.context.jdbc.Sql +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.delete +import org.springframework.test.web.servlet.get +import org.springframework.test.web.servlet.post +import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import org.springframework.transaction.annotation.Transactional +import org.springframework.web.context.WebApplicationContext +import util.objectMapper + +@SpringBootTest(classes = [SpringApplication::class]) +@AutoConfigureMockMvc +@Transactional +@Sql("/sql/actors.sql", "/sql/userdetail.sql","/sql/filter/test-filter.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) +class FilterTest { + @Autowired + private lateinit var context: WebApplicationContext + + private lateinit var mockMvc: MockMvc + + @BeforeEach + fun setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(context) + .apply(SecurityMockMvcConfigurers.springSecurity()) + .build() + } + + @Test + fun `apiV2FiltersPost write権限で追加できる`() { + mockMvc + .post("/api/v2/filters") { + contentType = MediaType.APPLICATION_JSON + content = objectMapper().writeValueAsString( + FilterPostRequest( + title = "mute test", + context = listOf(FilterPostRequest.Context.home, FilterPostRequest.Context.public), + filterAction = FilterPostRequest.FilterAction.warn, + expiresIn = null, + keywordsAttributes = listOf( + FilterPostRequestKeyword( + keyword = "hoge", + wholeWord = false, + regex = true + ) + ) + ) + ) + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + .andExpect { + content { + jsonPath("$.keywords[0].keyword") { + value("hoge") + } + } + } + } + + @Test + fun `apiV2FiltersPost write_filters権限で追加できる`() { + mockMvc + .post("/api/v2/filters") { + contentType = MediaType.APPLICATION_JSON + content = objectMapper().writeValueAsString( + FilterPostRequest( + title = "mute test", + context = listOf(FilterPostRequest.Context.home, FilterPostRequest.Context.public), + filterAction = FilterPostRequest.FilterAction.warn, + expiresIn = null, + keywordsAttributes = listOf( + FilterPostRequestKeyword( + keyword = "fuga", + wholeWord = true, + regex = false + ) + ) + ) + ) + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:filters")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + .andExpect { + content { + jsonPath("$.keywords[0].keyword") { + value("fuga") + } + } + } + } + + @Test + fun `apiV2FiltersPost read権限で401`() { + mockMvc + .post("/api/v2/filters") { + contentType = MediaType.APPLICATION_JSON + content = objectMapper().writeValueAsString( + FilterPostRequest( + title = "mute test", + context = listOf(FilterPostRequest.Context.home, FilterPostRequest.Context.public), + filterAction = FilterPostRequest.FilterAction.warn, + expiresIn = null, + keywordsAttributes = listOf( + FilterPostRequestKeyword( + keyword = "fuga", + wholeWord = true, + regex = false + ) + ) + ) + ) + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .andExpect { status { isForbidden() } } + } + + @Test + fun `apiV2FiltersGet read権限で取得できる`() { + mockMvc + .get("/api/v2/filters") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV2FiltersGet read_filters権限で取得できる`() { + mockMvc + .get("/api/v2/filters") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:filters")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV2FiltersGet write権限で401`() { + mockMvc + .get("/api/v2/filters") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) + ) + } + .andExpect { status { isForbidden() } } + } + + @Test + fun `apiV2FiltersIdGet read権限で取得できる`() { + mockMvc + .get("/api/v2/filters/1") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + + @Test + fun `apiV2FiltersIdGet read_filters権限で取得できる`() { + mockMvc + .get("/api/v2/filters/1") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:filters")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV2FiltersIdGet write権限で401`() { + mockMvc + .get("/api/v2/filters/1") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) + ) + } + .andExpect { status { isForbidden() } } + } + + @Test + fun `apiV2FiltersFilterIdKeywordsGet read権限で取得できる`() { + mockMvc + .get("/api/v2/filters/1/keywords") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV2FiltersFilterIdKeywordsGet read_filters権限で取得できる`() { + mockMvc + .get("/api/v2/filters/1/keywords") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:filters")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV2FiltersFilterIdKeywordsGet writeで403`() { + mockMvc + .get("/api/v2/filters/1/keywords") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) + ) + } + .andExpect { status { isForbidden() } } + } + + @Test + fun `apiV2FiltersFilterIdKeywordsPost writeで追加できる`() { + mockMvc + .post("/api/v2/filters/1/keywords") { + contentType = MediaType.APPLICATION_JSON + content = objectMapper().writeValueAsString( + FilterKeywordsPostRequest( + "hage", false, false + ) + ) + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV2FiltersFilterIdKeywordsPost write_filtersで追加できる`() { + mockMvc + .post("/api/v2/filters/1/keywords") { + contentType = MediaType.APPLICATION_JSON + content = objectMapper().writeValueAsString( + FilterKeywordsPostRequest( + "hage", false, false + ) + ) + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:filters")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV2FiltersFilterIdKeywordsPost readで403`() { + mockMvc + .post("/api/v2/filters/1/keywords") { + contentType = MediaType.APPLICATION_JSON + content = objectMapper().writeValueAsString( + FilterKeywordsPostRequest( + "hage", false, false + ) + ) + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .andExpect { status { isForbidden() } } + } + + @Test + fun `apiV2FiltersKeywordsIdGet readで取得できる`() { + mockMvc + .get("/api/v2/filters/keywords/1") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV2FiltersKeywordsIdGet read_filtersで取得できる`() { + mockMvc + .get("/api/v2/filters/keywords/1") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:filters")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV2FiltersKeywordsIdGet writeだと403`() { + mockMvc + .get("/api/v2/filters/keywords/1") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) + ) + } + .andExpect { status { isForbidden() } } + } + + @Test + fun `apiV2FiltersKeyowrdsIdDelete writeで削除できる`() = runTest { + mockMvc + .delete("/api/v2/filters/keywords/1") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV2FiltersKeyowrdsIdDelete write_filtersで削除できる`() = runTest { + mockMvc + .delete("/api/v2/filters/keywords/1") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:filters")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV2FiltersKeyowrdsIdDelete readで403`() = runTest { + mockMvc + .delete("/api/v2/filters/keywords/1") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .andExpect { status { isForbidden() } } + } + + @Test + fun `apiV2FiltersFilterIdStatuses readで取得できる`() { + mockMvc + .get("/api/v2/filters/1/statuses") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .asyncDispatch() + .andExpect { status { isNotFound() } } + } + + @Test + fun `apiV2FiltersFilterIdStatuses read_filtersで取得できる`() { + mockMvc + .get("/api/v2/filters/1/statuses") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:filters")) + ) + } + .asyncDispatch() + .andExpect { status { isNotFound() } } + } + + @Test + fun `apiV2FiltersFilterIdStatuses writeで403`() { + mockMvc + .get("/api/v2/filters/1/statuses") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) + ) + } + .andExpect { status { isForbidden() } } + } + + @Test + fun `apiV2FiltersStatusesIdGet readで取得できる`() { + mockMvc + .get("/api/v2/filters/statuses/1") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .asyncDispatch() + .andExpect { status { isNotFound() } } + } + + @Test + fun `apiV2FiltersStatusesIdGet read_filtersで取得できる`() { + mockMvc + .get("/api/v2/filters/statuses/1") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:filters")) + ) + } + .asyncDispatch() + .andExpect { status { isNotFound() } } + } + + @Test + fun `apiV2FiltersStatusesIdGet writeで403`() { + mockMvc + .get("/api/v2/filters/statuses/1") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) + ) + } + .andExpect { status { isForbidden() } } + } + + @Test + fun `apiV2FiltersStatusesIdDelete writeで削除できる`() { + mockMvc + .delete("/api/v2/filters/statuses/1") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) + ) + } + .asyncDispatch() + .andExpect { status { isNotFound() } } + } + + @Test + fun `apiV2FiltersStatusesIdDelete write_filtersで削除できる`() { + mockMvc + .delete("/api/v2/filters/statuses/1") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:filters")) + ) + } + .asyncDispatch() + .andExpect { status { isNotFound() } } + } + + @Test + fun `apiV2FiltersStatusesIdDelete readで403`() { + mockMvc + .delete("/api/v2/filters/statuses/1") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .andExpect { status { isForbidden() } } + } + + @Test + fun `apiV1FiltersGet readで取得できる`() { + mockMvc + .get("/api/v1/filters") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV1FiltersGet read_filtersで取得できる`() { + mockMvc + .get("/api/v1/filters") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:filters")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV1FiltersGet writeで403`() { + mockMvc + .get("/api/v1/filters") { + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) + ) + } + .andExpect { status { isForbidden() } } + } + + @Test + fun `apiV1FiltersPost writeで新規作成`() { + mockMvc + .post("/api/v1/filters") { + contentType = MediaType.APPLICATION_JSON + content = objectMapper().writeValueAsString( + V1FilterPostRequest( + phrase = "hoge", + context = listOf(V1FilterPostRequest.Context.home), + irreversible = false, + wholeWord = false, + expiresIn = null + ) + ) + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV1FiltersPost write_filtersで新規作成`() { + mockMvc + .post("/api/v1/filters") { + contentType = MediaType.APPLICATION_JSON + content = objectMapper().writeValueAsString( + V1FilterPostRequest( + phrase = "hoge", + context = listOf(V1FilterPostRequest.Context.home), + irreversible = false, + wholeWord = false, + expiresIn = null + ) + ) + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:filters")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV1FiltersPost readで403`() { + mockMvc + .post("/api/v1/filters") { + contentType = MediaType.APPLICATION_JSON + content = objectMapper().writeValueAsString( + V1FilterPostRequest( + phrase = "hoge", + context = listOf(V1FilterPostRequest.Context.home), + irreversible = false, + wholeWord = false, + expiresIn = null + ) + ) + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .andExpect { status { isForbidden() } } + } + + @Test + fun `apiV1FiltersIdGet readで取得できる`() { + mockMvc + .get("/api/v1/filters/1") { + with( + jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV1FiltersIdGet read_filtersで取得できる`() { + mockMvc + .get("/api/v1/filters/1") { + with( + jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:filters")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV1FiltersIdGet writeで403`() { + mockMvc + .get("/api/v1/filters/1") { + with( + jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) + ) + } + .andExpect { status { isForbidden() } } + } + + @Test + @Sql("/sql/filter/test-filter.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) + fun `apiV1FiltersIdDelete writeで削除できる`() { + mockMvc + .delete("/api/v1/filters/1") { + with( + jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + @Sql("/sql/filter/test-filter.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) + fun `apiV1FiltersIdDelete write_filtersで削除できる`() { + mockMvc + .delete("/api/v1/filters/1") { + with( + jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:filters")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV1FiltersIdDelete readで403`() { + mockMvc + .delete("/api/v1/filters/1") { + with( + jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .andExpect { status { isForbidden() } } + } + + companion object { + @JvmStatic + @AfterAll + fun dropDatabase(@Autowired flyway: Flyway) { + flyway.clean() + flyway.migrate() + } + } +} \ No newline at end of file diff --git a/hideout-mastodon/src/test/kotlin/mastodon/media/MediaTest.kt b/hideout-mastodon/src/test/kotlin/mastodon/media/MediaTest.kt new file mode 100644 index 00000000..7e4755d9 --- /dev/null +++ b/hideout-mastodon/src/test/kotlin/mastodon/media/MediaTest.kt @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package mastodon.media + +import dev.usbharu.hideout.SpringApplication +import kotlinx.coroutines.test.runTest +import org.flywaydb.core.Flyway +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.mock.web.MockMultipartFile +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt +import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers +import org.springframework.test.context.jdbc.Sql +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.multipart +import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import org.springframework.transaction.annotation.Transactional +import org.springframework.web.context.WebApplicationContext + +@SpringBootTest(classes = [SpringApplication::class]) +@AutoConfigureMockMvc +@Transactional +@Sql("/sql/actors.sql","/sql/userdetail.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) +class MediaTest { + + @Autowired + private lateinit var context: WebApplicationContext + + private lateinit var mockMvc: MockMvc + + @BeforeEach + fun setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(context) + .apply(SecurityMockMvcConfigurers.springSecurity()) + .build() + } + + @Test + fun メディアをアップロードできる() = runTest { + + + mockMvc + .multipart("/api/v1/media") { + + file( + MockMultipartFile( + "file", + "400x400.png", + "image/png", + String.javaClass.classLoader.getResourceAsStream("media/400x400.png") + ) + ) + with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun write_mediaスコープでメディアをアップロードできる() = runTest { + + + mockMvc + .multipart("/api/v1/media") { + + file( + MockMultipartFile( + "file", + "400x400.png", + "image/png", + String.javaClass.classLoader.getResourceAsStream("media/400x400.png") + ) + ) + with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:media"))) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun 権限がないと403() = runTest { + + + mockMvc + .multipart("/api/v1/media") { + + file( + MockMultipartFile( + "file", + "400x400.png", + "image/png", + String.javaClass.classLoader.getResourceAsStream("media/400x400.png") + ) + ) + with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read"))) + } + .andExpect { status { isForbidden() } } + } + + companion object { + @JvmStatic + @AfterAll + fun dropDatabase(@Autowired flyway: Flyway) { + flyway.clean() + flyway.migrate() + } + } + +} diff --git a/hideout-mastodon/src/test/kotlin/mastodon/notifications/ExposedNotificationsApiPaginationTest.kt b/hideout-mastodon/src/test/kotlin/mastodon/notifications/ExposedNotificationsApiPaginationTest.kt new file mode 100644 index 00000000..97f6f8f1 --- /dev/null +++ b/hideout-mastodon/src/test/kotlin/mastodon/notifications/ExposedNotificationsApiPaginationTest.kt @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package mastodon.notifications + +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import dev.usbharu.hideout.SpringApplication +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Notification +import kotlinx.coroutines.test.runTest +import org.assertj.core.api.Assertions.assertThat +import org.flywaydb.core.Flyway +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors +import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers +import org.springframework.test.context.jdbc.Sql +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.get +import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import org.springframework.transaction.annotation.Transactional +import org.springframework.web.context.WebApplicationContext + +@SpringBootTest(classes = [SpringApplication::class], properties = ["hideout.use-mongodb=false"]) +@AutoConfigureMockMvc +@Transactional +@Sql("/sql/actors.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) +//@Sql("/sql/notification/test-notifications.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) +//@Sql("/sql/notification/test-mastodon_notifications.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) +class ExposedNotificationsApiPaginationTest { + @Autowired + private lateinit var context: WebApplicationContext + + private lateinit var mockMvc: MockMvc + + @Test + fun `通知を取得できる`() = runTest { + val content = mockMvc + .get("/api/v1/notifications") { + with( + SecurityMockMvcRequestPostProcessors.jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .asyncDispatch() + .andExpect { + header { + string( + "Link", + "; rel=\"next\", ; rel=\"prev\"" + ) + } + } + .andReturn() + .response + .contentAsString + + val value = jacksonObjectMapper().readValue(content, object : TypeReference>() {}) + + assertThat(value.first().id).isEqualTo("65") + assertThat(value.last().id).isEqualTo("26") + } + + @Test + fun maxIdを指定して通知を取得できる() = runTest { + val content = mockMvc + .get("/api/v1/notifications?max_id=26") { + with( + SecurityMockMvcRequestPostProcessors.jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .asyncDispatch() + .andExpect { + header { + string( + "Link", + "; rel=\"next\", ; rel=\"prev\"" + ) + } + } + .andReturn() + .response + .contentAsString + + val value = jacksonObjectMapper().readValue(content, object : TypeReference>() {}) + + assertThat(value.first().id).isEqualTo("25") + assertThat(value.last().id).isEqualTo("1") + + } + + @Test + fun minIdを指定して通知を取得できる() = runTest { + val content = mockMvc + .get("/api/v1/notifications?min_id=25") { + with( + SecurityMockMvcRequestPostProcessors.jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .asyncDispatch() + .andExpect { + header { + string( + "Link", + "; rel=\"next\", ; rel=\"prev\"" + ) + } + } + .andReturn() + .response + .contentAsString + + val value = jacksonObjectMapper().readValue(content, object : TypeReference>() {}) + + assertThat(value.first().id).isEqualTo("65") + assertThat(value.last().id).isEqualTo("26") + } + + @Test + fun 結果が0件のときはページネーションのヘッダーがない() = runTest { + val content = mockMvc + .get("/api/v1/notifications?max_id=1") { + with( + SecurityMockMvcRequestPostProcessors.jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .asyncDispatch() + .andExpect { + header { + doesNotExist("Link") + } + } + .andReturn() + .response + .contentAsString + + val value = jacksonObjectMapper().readValue(content, object : TypeReference>() {}) + + assertThat(value).size().isZero() + } + + @BeforeEach + fun setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(context) + .apply(SecurityMockMvcConfigurers.springSecurity()) + .build() + } + + companion object { + @JvmStatic + @AfterAll + fun dropDatabase(@Autowired flyway: Flyway) { + flyway.clean() + flyway.migrate() + } + } +} \ No newline at end of file diff --git a/hideout-mastodon/src/test/kotlin/mastodon/notifications/MongodbNotificationsApiPaginationTest.kt b/hideout-mastodon/src/test/kotlin/mastodon/notifications/MongodbNotificationsApiPaginationTest.kt new file mode 100644 index 00000000..c0debcd5 --- /dev/null +++ b/hideout-mastodon/src/test/kotlin/mastodon/notifications/MongodbNotificationsApiPaginationTest.kt @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package mastodon.notifications + +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import dev.usbharu.hideout.SpringApplication +import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Notification +import kotlinx.coroutines.test.runTest +import org.assertj.core.api.Assertions +import org.flywaydb.core.Flyway +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors +import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers +import org.springframework.test.context.jdbc.Sql +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.get +import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import org.springframework.transaction.annotation.Transactional +import org.springframework.web.context.WebApplicationContext + +@SpringBootTest(classes = [SpringApplication::class], properties = ["hideout.use-mongodb=true"]) +@AutoConfigureMockMvc +@Transactional +@Sql("/sql/actors.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) +//@Sql("/sql/notification/test-notifications.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) +class MongodbNotificationsApiPaginationTest { + @Autowired + private lateinit var context: WebApplicationContext + + private lateinit var mockMvc: MockMvc + + @Test + fun `通知を取得できる`() = runTest { + val content = mockMvc + .get("/api/v1/notifications") { + with( + SecurityMockMvcRequestPostProcessors.jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .asyncDispatch() + .andDo { print() } + .andExpect { + header { + string( + "Link", + "; rel=\"next\", ; rel=\"prev\"" + ) + } + } + .andReturn() + .response + .contentAsString + + val value = jacksonObjectMapper().readValue(content, object : TypeReference>() {}) + + Assertions.assertThat(value.first().id).isEqualTo("65") + Assertions.assertThat(value.last().id).isEqualTo("26") + } + + @Test + fun maxIdを指定して通知を取得できる() = runTest { + val content = mockMvc + .get("/api/v1/notifications?max_id=26") { + with( + SecurityMockMvcRequestPostProcessors.jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .asyncDispatch() + .andExpect { + header { + string( + "Link", + "; rel=\"next\", ; rel=\"prev\"" + ) + } + } + .andReturn() + .response + .contentAsString + + val value = jacksonObjectMapper().readValue(content, object : TypeReference>() {}) + + Assertions.assertThat(value.first().id).isEqualTo("25") + Assertions.assertThat(value.last().id).isEqualTo("1") + + } + + @Test + fun minIdを指定して通知を取得できる() = runTest { + val content = mockMvc + .get("/api/v1/notifications?min_id=25") { + with( + SecurityMockMvcRequestPostProcessors.jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .asyncDispatch() + .andExpect { + header { + string( + "Link", + "; rel=\"next\", ; rel=\"prev\"" + ) + } + } + .andReturn() + .response + .contentAsString + + val value = jacksonObjectMapper().readValue(content, object : TypeReference>() {}) + + Assertions.assertThat(value.first().id).isEqualTo("65") + Assertions.assertThat(value.last().id).isEqualTo("26") + } + + @Test + fun 結果が0件のときはページネーションのヘッダーがない() = runTest { + val content = mockMvc + .get("/api/v1/notifications?max_id=1") { + with( + SecurityMockMvcRequestPostProcessors.jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .asyncDispatch() + .andExpect { + header { + doesNotExist("Link") + } + } + .andReturn() + .response + .contentAsString + + val value = jacksonObjectMapper().readValue(content, object : TypeReference>() {}) + + Assertions.assertThat(value).size().isZero() + } + + @BeforeEach + fun setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(context) + .apply(SecurityMockMvcConfigurers.springSecurity()) + .build() + } + + companion object { + @JvmStatic + @AfterAll + fun dropDatabase(@Autowired flyway: Flyway) { + flyway.clean() + flyway.migrate() + } + } +} \ No newline at end of file diff --git a/hideout-mastodon/src/test/kotlin/mastodon/status/StatusTest.kt b/hideout-mastodon/src/test/kotlin/mastodon/status/StatusTest.kt new file mode 100644 index 00000000..35a4a8a6 --- /dev/null +++ b/hideout-mastodon/src/test/kotlin/mastodon/status/StatusTest.kt @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package mastodon.status + +import dev.usbharu.hideout.SpringApplication +import dev.usbharu.hideout.core.domain.model.emoji.UnicodeEmoji +import dev.usbharu.hideout.core.infrastructure.exposedrepository.CustomEmojis +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Reactions +import dev.usbharu.hideout.core.infrastructure.exposedrepository.toReaction +import org.assertj.core.api.Assertions.assertThat +import org.flywaydb.core.Flyway +import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.selectAll +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.http.MediaType +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.test.context.support.WithAnonymousUser +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt +import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers +import org.springframework.test.context.jdbc.Sql +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.post +import org.springframework.test.web.servlet.put +import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import org.springframework.transaction.annotation.Transactional +import org.springframework.web.context.WebApplicationContext + +@SpringBootTest(classes = [SpringApplication::class]) +@AutoConfigureMockMvc +@Transactional +@Sql("/sql/actors.sql", "/sql/userdetail.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) +@Sql("/sql/posts.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) +@Sql("/sql/test-custom-emoji.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) +class StatusTest { + + @Autowired + private lateinit var context: WebApplicationContext + + private lateinit var mockMvc: MockMvc + + + @BeforeEach + fun setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(context) + .apply(SecurityMockMvcConfigurers.springSecurity()) + .build() + } + + @Test + fun 投稿できる() { + mockMvc + .post("/api/v1/statuses") { + contentType = MediaType.APPLICATION_JSON + content = """{"status":"hello"}""" + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun write_statusesスコープで投稿できる() { + mockMvc + .post("/api/v1/statuses") { + contentType = MediaType.APPLICATION_JSON + content = """{"status":"hello"}""" + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:statuses")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun 権限がないと403() { + mockMvc + .post("/api/v1/statuses") { + contentType = MediaType.APPLICATION_JSON + content = """{"status":"hello"}""" + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .andExpect { status { isForbidden() } } + } + + @Test + @WithAnonymousUser + fun 匿名だと401() { + mockMvc + .post("/api/v1/statuses") { + contentType = MediaType.APPLICATION_JSON + content = """{"status":"hello"}""" + with(csrf()) + } + .andExpect { status { isUnauthorized() } } + } + + @Test + @WithAnonymousUser + fun 匿名の場合通常はcsrfが無いので403() { + mockMvc + .post("/api/v1/statuses") { + contentType = MediaType.APPLICATION_JSON + content = """{"status":"hello"}""" + } + .andExpect { status { isForbidden() } } + } + + @Test + fun formでも投稿できる() { + mockMvc + .post("/api/v1/statuses") { + contentType = MediaType.APPLICATION_FORM_URLENCODED + param("status", "hello") + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write:statuses")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun in_reply_to_idを指定したら返信として処理される() { + mockMvc + .post("/api/v1/statuses") { + contentType = MediaType.APPLICATION_JSON + //language=JSON + content = """{ + "status": "hello", + "in_reply_to_id": "1" +}""" + with( + jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write")) + ) + } + .asyncDispatch() + .andDo { print() } + .andExpect { status { isOk() } } + .andExpect { jsonPath("\$.in_reply_to_id") { value("1") } } + } + + @Test + fun ユニコード絵文字をリアクションできる() { + mockMvc + .put("/api/v1/statuses/1/emoji_reactions/😭") { + with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))) + } + .andDo { print() } + .asyncDispatch() + .andExpect { status { isOk() } } + + val reaction = + Reactions.selectAll().where { Reactions.postId eq 1 and (Reactions.actorId eq 1) }.single().toReaction() + assertThat(reaction.unicodeEmoji).isEqualTo(UnicodeEmoji("😭")) + assertThat(reaction.postId).isEqualTo(1) + assertThat(reaction.actorId).isEqualTo(1) + } + + @Test + fun 存在しない絵文字はフォールバックされる() { + mockMvc + .put("/api/v1/statuses/1/emoji_reactions/hoge") { + with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))) + } + .andDo { print() } + .asyncDispatch() + .andExpect { status { isOk() } } + + val reaction = + Reactions.selectAll().where { Reactions.postId eq 1 and (Reactions.actorId eq 1) }.single().toReaction() + assertThat(reaction.unicodeEmoji).isEqualTo(UnicodeEmoji("❤")) + assertThat(reaction.postId).isEqualTo(1) + assertThat(reaction.actorId).isEqualTo(1) + } + + @Test + fun カスタム絵文字をリアクションできる() { + mockMvc + .put("/api/v1/statuses/1/emoji_reactions/kotlin") { + with(jwt().jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_write"))) + } + .andDo { print() } + .asyncDispatch() + .andExpect { status { isOk() } } + + val reaction = + Reactions.leftJoin(CustomEmojis).selectAll().where { Reactions.postId eq 1 and (Reactions.actorId eq 1) } + .single() + .toReaction() + + } + + companion object { + @JvmStatic + @AfterAll + fun dropDatabase(@Autowired flyway: Flyway) { + flyway.clean() + flyway.migrate() + } + } +} diff --git a/hideout-mastodon/src/test/kotlin/mastodon/timelines/TimelineApiTest.kt b/hideout-mastodon/src/test/kotlin/mastodon/timelines/TimelineApiTest.kt new file mode 100644 index 00000000..3f6bf40a --- /dev/null +++ b/hideout-mastodon/src/test/kotlin/mastodon/timelines/TimelineApiTest.kt @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package mastodon.timelines + +import dev.usbharu.hideout.SpringApplication +import org.flywaydb.core.Flyway +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.test.context.support.WithAnonymousUser +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors +import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers +import org.springframework.test.context.jdbc.Sql +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.get +import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import org.springframework.transaction.annotation.Transactional +import org.springframework.web.context.WebApplicationContext + +@SpringBootTest(classes = [SpringApplication::class]) +@Transactional +@Sql("/sql/actors.sql", "/sql/userdetail.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) +class TimelineApiTest { + @Autowired + private lateinit var context: WebApplicationContext + + private lateinit var mockMvc: MockMvc + + @BeforeEach + fun beforeEach() { + mockMvc = MockMvcBuilders.webAppContextSetup(context) + .apply(SecurityMockMvcConfigurers.springSecurity()) + .build() + } + + @Test + fun `apiV1TimelinesHomeGetにreadでアクセスできる`() { + mockMvc + .get("/api/v1/timelines/home") { + with( + SecurityMockMvcRequestPostProcessors.jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV1TimelinesHomeGetにread statusesでアクセスできる`() { + mockMvc + .get("/api/v1/timelines/home") { + with( + SecurityMockMvcRequestPostProcessors.jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:statuses")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + @WithAnonymousUser + fun apiV1TimelineHomeGetに匿名でアクセスすると401() { + mockMvc + .get("/api/v1/timelines/home") + .andExpect { status { isUnauthorized() } } + } + + @Test + fun apiV1TimelinesPublicGetにreadでアクセスできる() { + mockMvc + .get("/api/v1/timelines/public") { + with( + SecurityMockMvcRequestPostProcessors.jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV1TimelinesPublicGetにread statusesでアクセスできる`() { + mockMvc + .get("/api/v1/timelines/public") { + with( + SecurityMockMvcRequestPostProcessors.jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:statuses")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + @WithAnonymousUser + fun apiV1TimeinesPublicGetに匿名でアクセスできる() { + mockMvc + .get("/api/v1/timelines/public") + .asyncDispatch() + .andExpect { status { isOk() } } + } + + companion object { + @JvmStatic + @AfterAll + fun dropDatabase(@Autowired flyway: Flyway) { + flyway.clean() + flyway.migrate() + } + } +} diff --git a/hideout-mastodon/src/test/kotlin/util/Json.kt b/hideout-mastodon/src/test/kotlin/util/Json.kt new file mode 100644 index 00000000..fe49c2ca --- /dev/null +++ b/hideout-mastodon/src/test/kotlin/util/Json.kt @@ -0,0 +1,8 @@ +package util + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper + +fun objectMapper(): ObjectMapper { + return jacksonObjectMapper() +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/followtimeline/FollowTimelineRepository.kt b/hideout-mastodon/src/test/kotlin/util/SpringApplicationTestBase.kt similarity index 72% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/followtimeline/FollowTimelineRepository.kt rename to hideout-mastodon/src/test/kotlin/util/SpringApplicationTestBase.kt index 92273f70..158f8c7a 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/followtimeline/FollowTimelineRepository.kt +++ b/hideout-mastodon/src/test/kotlin/util/SpringApplicationTestBase.kt @@ -14,9 +14,9 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.domain.model.followtimeline +package util -interface FollowTimelineRepository { - suspend fun save(followTimeline: FollowTimeline): FollowTimeline - suspend fun delete(followTimeline: FollowTimeline) -} +import org.springframework.boot.test.context.SpringBootTest + +@SpringBootTest +abstract class SpringApplicationTestBase diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/followtimeline/FollowTimeline.kt b/hideout-mastodon/src/test/kotlin/util/TestTransaction.kt similarity index 66% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/followtimeline/FollowTimeline.kt rename to hideout-mastodon/src/test/kotlin/util/TestTransaction.kt index 463e4c32..8830ba4e 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/followtimeline/FollowTimeline.kt +++ b/hideout-mastodon/src/test/kotlin/util/TestTransaction.kt @@ -14,9 +14,13 @@ * limitations under the License. */ -package dev.usbharu.hideout.core.domain.model.followtimeline +package util -import dev.usbharu.hideout.core.domain.model.timeline.TimelineId -import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId +import dev.usbharu.hideout.core.application.shared.Transaction -class FollowTimeline(val userDetailId: UserDetailId, val timelineId: TimelineId) + +object TestTransaction : Transaction { + override suspend fun transaction(block: suspend () -> T): T = block() + + override suspend fun transaction(transactionLevel: Int, block: suspend () -> T): T = block() +} diff --git a/hideout-mastodon/src/test/kotlin/util/WithHttpSignature.kt b/hideout-mastodon/src/test/kotlin/util/WithHttpSignature.kt new file mode 100644 index 00000000..d7e58abc --- /dev/null +++ b/hideout-mastodon/src/test/kotlin/util/WithHttpSignature.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package util + +//@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE) +//@Retention(AnnotationRetention.RUNTIME) +//@Inherited +//@MustBeDocumented +//@WithSecurityContext(factory = WithHttpSignatureSecurityContextFactory::class) +//annotation class WithHttpSignature( +// @get:AliasFor( +// annotation = WithSecurityContext::class +// ) val setupBefore: TestExecutionEvent = TestExecutionEvent.TEST_METHOD, +// val keyId: String = "https://example.com/users/test-user#pubkey", +// val url: String = "https://example.com/inbox", +// val method: String = "GET" +//) diff --git a/hideout-mastodon/src/test/kotlin/util/WithHttpSignatureSecurityContextFactory.kt b/hideout-mastodon/src/test/kotlin/util/WithHttpSignatureSecurityContextFactory.kt new file mode 100644 index 00000000..626cf79e --- /dev/null +++ b/hideout-mastodon/src/test/kotlin/util/WithHttpSignatureSecurityContextFactory.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package util + +//class WithHttpSignatureSecurityContextFactory( +// private val actorRepository: ActorRepository, +// private val transaction: Transaction +//) : WithSecurityContextFactory { +// +// private val securityContextStrategy = SecurityContextHolder.getContextHolderStrategy() +// +// override fun createSecurityContext(annotation: WithHttpSignature): SecurityContext = runBlocking { +// val preAuthenticatedAuthenticationToken = PreAuthenticatedAuthenticationToken( +// annotation.keyId, HttpRequest( +// URL("https://example.com/inbox"), +// HttpHeaders(mapOf()), HttpMethod.GET +// ) +// ) +// val httpSignatureUser = transaction.transaction { +// val findByKeyId = +// actorRepository.findByKeyId(annotation.keyId) ?: throw IllegalArgumentException(annotation.keyId) +// HttpSignatureUser( +// findByKeyId.name, +// findByKeyId.domain, +// findByKeyId.id, +// true, +// true, +// mutableListOf() +// ) +// } +// preAuthenticatedAuthenticationToken.details = httpSignatureUser +// preAuthenticatedAuthenticationToken.isAuthenticated = true +// val emptyContext = securityContextStrategy.createEmptyContext() +// emptyContext.authentication = preAuthenticatedAuthenticationToken +// return@runBlocking emptyContext +// } +// +//} diff --git a/hideout-mastodon/src/test/kotlin/util/WithMockHttpSignature.kt b/hideout-mastodon/src/test/kotlin/util/WithMockHttpSignature.kt new file mode 100644 index 00000000..fcaba839 --- /dev/null +++ b/hideout-mastodon/src/test/kotlin/util/WithMockHttpSignature.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package util + +// +//@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE) +//@Retention(AnnotationRetention.RUNTIME) +//@Inherited +//@MustBeDocumented +//@WithSecurityContext(factory = WithMockHttpSignatureSecurityContextFactory::class) +//annotation class WithMockHttpSignature( +// @get:AliasFor( +// annotation = WithSecurityContext::class +// ) val setupBefore: TestExecutionEvent = TestExecutionEvent.TEST_METHOD, +// val username: String = "test-user", +// val domain: String = "example.com", +// val keyId: String = "https://example.com/users/test-user#pubkey", +// val id: Long = 1234L, +// val url: String = "https://example.com/inbox", +// val method: String = "GET" +//) diff --git a/hideout-mastodon/src/test/kotlin/util/WithMockHttpSignatureSecurityContextFactory.kt b/hideout-mastodon/src/test/kotlin/util/WithMockHttpSignatureSecurityContextFactory.kt new file mode 100644 index 00000000..bf5218f6 --- /dev/null +++ b/hideout-mastodon/src/test/kotlin/util/WithMockHttpSignatureSecurityContextFactory.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package util +// +//import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureUser +//import dev.usbharu.httpsignature.common.HttpHeaders +//import dev.usbharu.httpsignature.common.HttpMethod +//import dev.usbharu.httpsignature.common.HttpRequest +//import org.springframework.security.core.context.SecurityContext +//import org.springframework.security.core.context.SecurityContextHolder +//import org.springframework.security.test.context.support.WithSecurityContextFactory +//import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken +//import java.net.URL +// +//class WithMockHttpSignatureSecurityContextFactory : +// WithSecurityContextFactory { +// +// private val securityContextStrategy = SecurityContextHolder.getContextHolderStrategy() +// +// override fun createSecurityContext(annotation: WithMockHttpSignature): SecurityContext { +// val preAuthenticatedAuthenticationToken = PreAuthenticatedAuthenticationToken( +// annotation.keyId, HttpRequest( +// URL(annotation.url), +// HttpHeaders(mapOf()), HttpMethod.valueOf(annotation.method.uppercase()) +// ) +// ) +// val httpSignatureUser = HttpSignatureUser( +// annotation.username, +// annotation.domain, +// annotation.id, +// true, +// true, +// mutableListOf() +// ) +// preAuthenticatedAuthenticationToken.details = httpSignatureUser +// preAuthenticatedAuthenticationToken.isAuthenticated = true +// val emptyContext = securityContextStrategy.createEmptyContext() +// emptyContext.authentication = preAuthenticatedAuthenticationToken +// return emptyContext +// } +//} diff --git a/hideout-mastodon/src/test/resources/application.yml b/hideout-mastodon/src/test/resources/application.yml index 05671a3f..0f4fd0e1 100644 --- a/hideout-mastodon/src/test/resources/application.yml +++ b/hideout-mastodon/src/test/resources/application.yml @@ -2,6 +2,8 @@ spring: datasource: url: "jdbc:h2:mem:test;MODE=POSTGRESQL;DB_CLOSE_DELAY=-1;CASE_INSENSITIVE_IDENTIFIERS=true;TRACE_LEVEL_FILE=4;" driver-class-name: org.h2.Driver + flyway: + clean-disabled: false hideout: url: "https://test-hideout-dev.usbharu.dev" security: @@ -9,4 +11,4 @@ hideout: generate: true key-id: a private-key: "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7VJTUt9Us8cKjMzEfYyjiWA4R4/M2bS1GB4t7NXp98C3SC6dVMvDuictGeurT8jNbvJZHtCSuYEvuNMoSfm76oqFvAp8Gy0iz5sxjZmSnXyCdPEovGhLa0VzMaQ8s+CLOyS56YyCFGeJZqgtzJ6GR3eqoYSW9b9UMvkBpZODSctWSNGj3P7jRFDO5VoTwCQAWbFnOjDfH5Ulgp2PKSQnSJP3AJLQNFNe7br1XbrhV//eO+t51mIpGSDCUv3E0DDFcWDTH9cXDTTlRZVEiR2BwpZOOkE/Z0/BVnhZYL71oZV34bKfWjQIt6V/isSMahdsAASACp4ZTGtwiVuNd9tybAgMBAAECggEBAKTmjaS6tkK8BlPXClTQ2vpz/N6uxDeS35mXpqasqskVlaAidgg/sWqpjXDbXr93otIMLlWsM+X0CqMDgSXKejLS2jx4GDjI1ZTXg++0AMJ8sJ74pWzVDOfmCEQ/7wXs3+cbnXhKriO8Z036q92Qc1+N87SI38nkGa0ABH9CN83HmQqt4fB7UdHzuIRe/me2PGhIq5ZBzj6h3BpoPGzEP+x3l9YmK8t/1cN0pqI+dQwYdgfGjackLu/2qH80MCF7IyQaseZUOJyKrCLtSD/Iixv/hzDEUPfOCjFDgTpzf3cwta8+oE4wHCo1iI1/4TlPkwmXx4qSXtmw4aQPz7IDQvECgYEA8KNThCO2gsC2I9PQDM/8Cw0O983WCDY+oi+7JPiNAJwv5DYBqEZB1QYdj06YD16XlC/HAZMsMku1na2TN0driwenQQWzoev3g2S7gRDoS/FCJSI3jJ+kjgtaA7Qmzlgk1TxODN+G1H91HW7t0l7VnL27IWyYo2qRRK3jzxqUiPUCgYEAx0oQs2reBQGMVZnApD1jeq7n4MvNLcPvt8b/eU9iUv6Y4Mj0Suo/AU8lYZXm8ubbqAlwz2VSVunD2tOplHyMUrtCtObAfVDUAhCndKaA9gApgfb3xw1IKbuQ1u4IF1FJl3VtumfQn//LiH1B3rXhcdyo3/vIttEk48RakUKClU8CgYEAzV7W3COOlDDcQd935DdtKBFRAPRPAlspQUnzMi5eSHMD/ISLDY5IiQHbIH83D4bvXq0X7qQoSBSNP7Dvv3HYuqMhf0DaegrlBuJllFVVq9qPVRnKxt1Il2HgxOBvbhOT+9in1BzA+YJ99UzC85O0Qz06A+CmtHEy4aZ2kj5hHjECgYEAmNS4+A8Fkss8Js1RieK2LniBxMgmYml3pfVLKGnzmng7H2+cwPLhPIzIuwytXywh2bzbsYEfYx3EoEVgMEpPhoarQnYPukrJO4gwE2o5Te6T5mJSZGlQJQj9q4ZB2Dfzet6INsK0oG8XVGXSpQvQh3RUYekCZQkBBFcpqWpbIEsCgYAnM3DQf3FJoSnXaMhrVBIovic5l0xFkEHskAjFTevO86Fsz1C2aSeRKSqGFoOQ0tmJzBEs1R6KqnHInicDTQrKhArgLXX4v3CddjfTRJkFWDbE/CkvKZNOrcf1nhaGCPspRJj2KUkj1Fhl9Cncdn/RsYEONbwQSjIfMPkvxF+8HQ==" - public-key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyehkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdgcKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbcmwIDAQAB" \ No newline at end of file + public-key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyehkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdgcKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbcmwIDAQAB" diff --git a/hideout-mastodon/src/test/resources/media/400x400.png b/hideout-mastodon/src/test/resources/media/400x400.png new file mode 100644 index 00000000..0d2e71be Binary files /dev/null and b/hideout-mastodon/src/test/resources/media/400x400.png differ diff --git a/hideout-mastodon/src/test/resources/sql/accounts/apiV1AccountsIdFollowPost フォローできる.sql b/hideout-mastodon/src/test/resources/sql/accounts/apiV1AccountsIdFollowPost フォローできる.sql new file mode 100644 index 00000000..a2b01c22 --- /dev/null +++ b/hideout-mastodon/src/test/resources/sql/accounts/apiV1AccountsIdFollowPost フォローできる.sql @@ -0,0 +1,17 @@ +insert into "actors" (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, + created_at, key_id, following, followers, instance, locked, following_count, followers_count, + posts_count, last_post_at) +VALUES (3733363, 'follow-test-user-1', 'example.com', 'follow-test-user-1-name', '', + 'https://example.com/users/follow-test-user-1/inbox', + 'https://example.com/users/follow-test-user-1/outbox', 'https://example.com/users/follow-test-user-1', + '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', + '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, + 'https://example.com/users/follow-test-user-1#pubkey', 'https://example.com/users/follow-test-user-1/following', + 'https://example.com/users/follow-test-user-1/followers', 0, false, 0, 0, 0, null), + (37335363, 'follow-test-user-2', 'example.com', 'follow-test-user-2-name', '', + 'https://example.com/users/follow-test-user-2/inbox', + 'https://example.com/users/follow-test-user-2/outbox', 'https://example.com/users/follow-test-user-2', + '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', + '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, + 'https://example.com/users/follow-test-user-2#pubkey', 'https://example.com/users/follow-test-user-2/following', + 'https://example.com/users/follow-test-user-2/followers', 0, false, 0, 0, 0, null); diff --git a/hideout-mastodon/src/test/resources/sql/accounts/test-accounts-statuses.sql b/hideout-mastodon/src/test/resources/sql/accounts/test-accounts-statuses.sql new file mode 100644 index 00000000..058a3e81 --- /dev/null +++ b/hideout-mastodon/src/test/resources/sql/accounts/test-accounts-statuses.sql @@ -0,0 +1,201 @@ +insert into posts (id, actor_id, instance_id, overview, content, text, created_at, visibility, url, repost_id, reply_id, sensitive, ap_id, deleted, hide, move_to) +VALUES (1, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/1', + null, null, false, 'https://example.com/users/1/posts/1', false,false,null), + (2, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/2', + null, 1, false, 'https://example.com/users/1/posts/2', false,false,null), + (3, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/3', + null, null, false, 'https://example.com/users/1/posts/3', false,false,null), + (4, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/4', + null, 3, false, 'https://example.com/users/1/posts/4', false,false,null), + (5, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/5', + null, null, false, 'https://example.com/users/1/posts/5', false,false,null), + (6, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/6', + null, null, false, 'https://example.com/users/1/posts/6', false,false,null), + (7, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/7', + null, null, false, 'https://example.com/users/1/posts/7', false,false,null), + (8, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/8', + null, 7, false, 'https://example.com/users/1/posts/8', false,false,null), + (9, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/9', + null, null, false, 'https://example.com/users/1/posts/9', false,false,null), + (10, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/10', + null, 9, false, 'https://example.com/users/1/posts/10', false,false,null), + (11, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/11', + null, null, false, 'https://example.com/users/1/posts/11', false,false,null), + (12, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/12', + null, null, false, 'https://example.com/users/1/posts/12', false,false,null), + (13, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/13', + null, null, false, 'https://example.com/users/1/posts/13', false,false,null), + (14, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/14', + null, 13, false, 'https://example.com/users/1/posts/14', false,false,null), + (15, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/15', + null, null, false, 'https://example.com/users/1/posts/15', false,false,null), + (16, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/16', + null, 15, false, 'https://example.com/users/1/posts/16', false,false,null), + (17, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/17', + null, null, false, 'https://example.com/users/1/posts/17', false,false,null), + (18, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/18', + null, null, false, 'https://example.com/users/1/posts/18', false,false,null), + (19, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/19', + null, null, false, 'https://example.com/users/1/posts/19', false,false,null), + (20, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/20', + null, 19, false, 'https://example.com/users/1/posts/20', false,false,null), + (21, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/21', + null, null, false, 'https://example.com/users/1/posts/21', false,false,null), + (22, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/22', + null, 21, false, 'https://example.com/users/1/posts/22', false,false,null), + (23, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/23', + null, null, false, 'https://example.com/users/1/posts/23', false,false,null), + (24, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/24', + null, null, false, 'https://example.com/users/1/posts/24', false,false,null), + (25, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/25', + null, null, false, 'https://example.com/users/1/posts/25', false,false,null), + (26, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/26', + null, 25, false, 'https://example.com/users/1/posts/26', false,false,null), + (27, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/27', + null, null, false, 'https://example.com/users/1/posts/27', false,false,null), + (28, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/28', + null, 27, false, 'https://example.com/users/1/posts/28', false,false,null), + (29, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/29', + null, null, false, 'https://example.com/users/1/posts/29', false,false,null), + (30, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/30', + null, null, false, 'https://example.com/users/1/posts/30', false,false,null), + (31, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/31', + null, null, false, 'https://example.com/users/1/posts/31', false,false,null), + (32, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/32', + null, 31, false, 'https://example.com/users/1/posts/32', false,false,null), + (33, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/33', + null, null, false, 'https://example.com/users/1/posts/33', false,false,null), + (34, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/34', + null, 33, false, 'https://example.com/users/1/posts/34', false,false,null), + (35, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/35', + null, null, false, 'https://example.com/users/1/posts/35', false,false,null), + (36, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/36', + null, null, false, 'https://example.com/users/1/posts/36', false,false,null), + (37, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/37', + null, null, false, 'https://example.com/users/1/posts/37', false,false,null), + (38, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/38', + null, 37, false, 'https://example.com/users/1/posts/38', false,false,null), + (39, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/39', + null, null, false, 'https://example.com/users/1/posts/39', false,false,null), + (40, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/40', + null, 39, false, 'https://example.com/users/1/posts/40', false,false,null), + (41, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/41', + null, null, false, 'https://example.com/users/1/posts/41', false,false,null), + (42, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/42', + null, null, false, 'https://example.com/users/1/posts/42', false,false,null), + (43, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/43', + null, null, false, 'https://example.com/users/1/posts/43', false,false,null), + (44, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/44', + null, 43, false, 'https://example.com/users/1/posts/44', false,false,null), + (45, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/45', + null, null, false, 'https://example.com/users/1/posts/45', false,false,null), + (46, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/46', + null, 45, false, 'https://example.com/users/1/posts/46', false,false,null), + (47, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/47', + null, null, false, 'https://example.com/users/1/posts/47', false,false,null), + (48, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/48', + null, null, false, 'https://example.com/users/1/posts/48', false,false,null), + (49, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/49', + null, null, false, 'https://example.com/users/1/posts/49', false,false,null), + (50, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/50', + null, 49, false, 'https://example.com/users/1/posts/50', false,false,null), + (51, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/51', + null, null, false, 'https://example.com/users/1/posts/51', false,false,null), + (52, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/52', + null, 51, false, 'https://example.com/users/1/posts/52', false,false,null), + (53, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/53', + null, null, false, 'https://example.com/users/1/posts/53', false,false,null), + (54, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/54', + null, null, false, 'https://example.com/users/1/posts/54', false,false,null), + (55, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/55', + null, null, false, 'https://example.com/users/1/posts/55', false,false,null), + (56, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/56', + null, 55, false, 'https://example.com/users/1/posts/56', false,false,null), + (57, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/57', + null, null, false, 'https://example.com/users/1/posts/57', false,false,null), + (58, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/58', + null, 57, false, 'https://example.com/users/1/posts/58', false,false,null), + (59, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/59', + null, null, false, 'https://example.com/users/1/posts/59', false,false,null), + (60, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/60', + null, null, false, 'https://example.com/users/1/posts/60', false,false,null), + (61, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/61', + null, null, false, 'https://example.com/users/1/posts/61', false,false,null), + (62, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/62', + null, 61, false, 'https://example.com/users/1/posts/62', false,false,null), + (63, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/63', + null, null, false, 'https://example.com/users/1/posts/63', false,false,null), + (64, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/64', + null, 63, false, 'https://example.com/users/1/posts/64', false,false,null), + (65, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/65', + null, null, false, 'https://example.com/users/1/posts/65', false,false,null), + (66, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/66', + null, null, false, 'https://example.com/users/1/posts/66', false,false,null), + (67, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/67', + null, null, false, 'https://example.com/users/1/posts/67', false,false,null), + (68, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/68', + null, 67, false, 'https://example.com/users/1/posts/68', false,false,null), + (69, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/69', + null, null, false, 'https://example.com/users/1/posts/69', false,false,null), + (70, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/70', + null, 69, false, 'https://example.com/users/1/posts/70', false,false,null), + (71, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/71', + null, null, false, 'https://example.com/users/1/posts/71', false,false,null), + (72, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/72', + null, null, false, 'https://example.com/users/1/posts/72', false,false,null), + (73, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/73', + null, null, false, 'https://example.com/users/1/posts/73', false,false,null), + (74, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/74', + null, 73, false, 'https://example.com/users/1/posts/74', false,false,null), + (75, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/75', + null, null, false, 'https://example.com/users/1/posts/75', false,false,null), + (76, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/76', + null, 75, false, 'https://example.com/users/1/posts/76', false,false,null), + (77, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/77', + null, null, false, 'https://example.com/users/1/posts/77', false,false,null), + (78, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/78', + null, null, false, 'https://example.com/users/1/posts/78', false,false,null), + (79, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/79', + null, null, false, 'https://example.com/users/1/posts/79', false,false,null), + (80, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/80', + null, 79, false, 'https://example.com/users/1/posts/80', false,false,null), + (81, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/81', + null, null, false, 'https://example.com/users/1/posts/81', false,false,null), + (82, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/82', + null, 81, false, 'https://example.com/users/1/posts/82', false,false,null), + (83, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/83', + null, null, false, 'https://example.com/users/1/posts/83', false,false,null), + (84, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/84', + null, null, false, 'https://example.com/users/1/posts/84', false,false,null), + (85, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/85', + null, null, false, 'https://example.com/users/1/posts/85', false,false,null), + (86, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/86', + null, 85, false, 'https://example.com/users/1/posts/86', false,false,null), + (87, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/87', + null, null, false, 'https://example.com/users/1/posts/87', false,false,null), + (88, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/88', + null, 87, false, 'https://example.com/users/1/posts/88', false,false,null), + (89, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/89', + null, null, false, 'https://example.com/users/1/posts/89', false,false,null), + (90, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/90', + null, null, false, 'https://example.com/users/1/posts/90', false,false,null), + (91, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/91', + null, null, false, 'https://example.com/users/1/posts/91', false,false,null), + (92, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/92', + null, 91, false, 'https://example.com/users/1/posts/92', false,false,null), + (93, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/93', + null, null, false, 'https://example.com/users/1/posts/93', false,false,null), + (94, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/94', + null, 93, false, 'https://example.com/users/1/posts/94', false,false,null), + (95, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/95', + null, null, false, 'https://example.com/users/1/posts/95', false,false,null), + (96, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/96', + null, null, false, 'https://example.com/users/1/posts/96', false,false,null), + (97, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/97', + null, null, false, 'https://example.com/users/1/posts/97', false,false,null), + (98, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/98', + null, 97, false, 'https://example.com/users/1/posts/98', false,false,null), + (99, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'FOLLOWERS', 'https://example.com/users/1/posts/99', + null, null, false, 'https://example.com/users/1/posts/99', false,false,null), + (100, 1,1,null, '

this is test

', 'this is test', current_timestamp, 'PUBLIC', 'https://example.com/users/1/posts/100', + null, 99, false, 'https://example.com/users/1/posts/100', false,false,null); \ No newline at end of file diff --git a/hideout-mastodon/src/test/resources/sql/filter/test-filter.sql b/hideout-mastodon/src/test/resources/sql/filter/test-filter.sql new file mode 100644 index 00000000..8eab045a --- /dev/null +++ b/hideout-mastodon/src/test/resources/sql/filter/test-filter.sql @@ -0,0 +1,4 @@ +insert into filters (id, user_id, name, context, action) +VALUES (1, 1, 'test filter', 'HOME', 'WARN'); +insert into filter_keywords(id, filter_id, keyword, mode) +VALUES (1, 1, 'hoge', 'NONE') \ No newline at end of file diff --git a/hideout-mastodon/src/test/resources/sql/note/httpSignature認証でフォロワーがfollowers投稿を取得できる.sql b/hideout-mastodon/src/test/resources/sql/note/httpSignature認証でフォロワーがfollowers投稿を取得できる.sql new file mode 100644 index 00000000..dc2ab9f1 --- /dev/null +++ b/hideout-mastodon/src/test/resources/sql/note/httpSignature認証でフォロワーがfollowers投稿を取得できる.sql @@ -0,0 +1,28 @@ +insert into "actors" (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, + created_at, key_id, following, followers, instance, locked, following_count, followers_count, + posts_count, last_post_at) +VALUES (8, 'test-user8', 'example.com', 'Im test-user8.', 'THis account is test-user8.', + 'https://example.com/users/test-user8/inbox', + 'https://example.com/users/test-user8/outbox', 'https://example.com/users/test-user8', + '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', + '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, + 'https://example.com/users/test-user8#pubkey', 'https://example.com/users/test-user8/following', + 'https://example.com/users/test-user8/followers', 0, false, 0, 0, 0, null), + (9, 'test-user9', 'follower.example.com', 'Im test-user9.', 'THis account is test-user9.', + 'https://follower.example.com/users/test-user9/inbox', + 'https://follower.example.com/users/test-user9/outbox', 'https://follower.example.com/users/test-user9', + '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', + null, 12345678, + 'https://follower.example.com/users/test-user9#pubkey', + 'https://follower.example.com/users/test-user9/following', + 'https://follower.example.com/users/test-user9/followers', 0, false, 0, 0, 0, null); + +insert into relationships (actor_id, target_actor_id, following, blocking, muting, follow_request, + ignore_follow_request) +VALUES (9, 8, true, false, false, false, false); + +insert into POSTS (ID, ACTOR_ID, OVERVIEW, CONTENT, TEXT, CREATED_AT, VISIBILITY, URL, REPLY_ID, REPOST_ID, SENSITIVE, + AP_ID) +VALUES (1239, 8, null, '

test post

', 'test post', 12345680, 2, 'https://example.com/users/test-user8/posts/1239', + null, null, false, + 'https://example.com/users/test-user8/posts/1239'); diff --git a/hideout-mastodon/src/test/resources/sql/note/httpSignature認証でフォロワーがpublic投稿を取得できる.sql b/hideout-mastodon/src/test/resources/sql/note/httpSignature認証でフォロワーがpublic投稿を取得できる.sql new file mode 100644 index 00000000..a44861f4 --- /dev/null +++ b/hideout-mastodon/src/test/resources/sql/note/httpSignature認証でフォロワーがpublic投稿を取得できる.sql @@ -0,0 +1,29 @@ +insert into "actors" (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, + created_at, key_id, following, followers, instance, locked, following_count, followers_count, + posts_count, last_post_at) +VALUES (4, 'test-user4', 'example.com', 'Im test user4.', 'THis account is test user4.', + 'https://example.com/users/test-user4/inbox', + 'https://example.com/users/test-user4/outbox', 'https://example.com/users/test-user4', + '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', + '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, + 'https://example.com/users/test-user4#pubkey', 'https://example.com/users/test-user4/following', + 'https://example.com/users/test-user4/followers', 0, false, 0, 0, 0, null), + (5, 'test-user5', 'follower.example.com', 'Im test user5.', 'THis account is test user5.', + 'https://follower.example.com/users/test-user5/inbox', + 'https://follower.example.com/users/test-user5/outbox', 'https://follower.example.com/users/test-user5', + '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', + null, 12345678, + 'https://follower.example.com/users/test-user5#pubkey', + 'https://follower.example.com/users/test-user5/following', + 'https://follower.example.com/users/test-user5/followers', 0, false, 0, 0, 0, null); + +insert into relationships (actor_id, target_actor_id, following, blocking, muting, follow_request, + ignore_follow_request) +VALUES (5, 4, true, false, false, false, false); + +insert into POSTS (ID, "actor_id", OVERVIEW, CONTENT, TEXT, "created_at", VISIBILITY, URL, "repost_id", "reply_id", + SENSITIVE, + AP_ID) +VALUES (1237, 4, null, '

test post

', 'test post', 12345680, 0, 'https://example.com/users/test-user4/posts/1237', + null, null, false, + 'https://example.com/users/test-user4/posts/1237'); diff --git a/hideout-mastodon/src/test/resources/sql/note/httpSignature認証でフォロワーがunlisted投稿を取得できる.sql b/hideout-mastodon/src/test/resources/sql/note/httpSignature認証でフォロワーがunlisted投稿を取得できる.sql new file mode 100644 index 00000000..77cb3395 --- /dev/null +++ b/hideout-mastodon/src/test/resources/sql/note/httpSignature認証でフォロワーがunlisted投稿を取得できる.sql @@ -0,0 +1,29 @@ +insert into "actors" (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, + created_at, key_id, following, followers, instance, locked, following_count, followers_count, + posts_count, last_post_at) +VALUES (6, 'test-user6', 'example.com', 'Im test-user6.', 'THis account is test-user6.', + 'https://example.com/users/test-user6/inbox', + 'https://example.com/users/test-user6/outbox', 'https://example.com/users/test-user6', + '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', + '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, + 'https://example.com/users/test-user6#pubkey', 'https://example.com/users/test-user6/following', + 'https://example.com/users/test-user6/followers', 0, false, 0, 0, 0, null), + (7, 'test-user7', 'follower.example.com', 'Im test-user7.', 'THis account is test-user7.', + 'https://follower.example.com/users/test-user7/inbox', + 'https://follower.example.com/users/test-user7/outbox', 'https://follower.example.com/users/test-user7', + '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', + null, 12345678, + 'https://follower.example.com/users/test-user7#pubkey', + 'https://follower.example.com/users/test-user7/following', + 'https://follower.example.com/users/test-user7/followers', 0, false, 0, 0, 0, null); + +insert into relationships (actor_id, target_actor_id, following, blocking, muting, follow_request, + ignore_follow_request) +VALUES (7, 6, true, false, false, false, false); + +insert into POSTS (ID, "actor_ID", OVERVIEW, CONTENT, TEXT, "CREATED_AT", VISIBILITY, URL, "REPOST_ID", "REPLY_ID", + SENSITIVE, + AP_ID) +VALUES (1238, 6, null, '

test post

', 'test post', 12345680, 1, 'https://example.com/users/test-user6/posts/1238', + null, null, false, + 'https://example.com/users/test-user6/posts/1238'); diff --git a/hideout-mastodon/src/test/resources/sql/note/メディア付き投稿はattachmentにDocumentとして画像が存在する.sql b/hideout-mastodon/src/test/resources/sql/note/メディア付き投稿はattachmentにDocumentとして画像が存在する.sql new file mode 100644 index 00000000..6cc9db8c --- /dev/null +++ b/hideout-mastodon/src/test/resources/sql/note/メディア付き投稿はattachmentにDocumentとして画像が存在する.sql @@ -0,0 +1,25 @@ +insert into "actors" (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, + created_at, key_id, following, followers, instance, locked, following_count, followers_count, + posts_count, last_post_at) +VALUES (11, 'test-user11', 'example.com', 'Im test-user11.', 'THis account is test-user11.', + 'https://example.com/users/test-user11/inbox', + 'https://example.com/users/test-user11/outbox', 'https://example.com/users/test-user11', + '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', + '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, + 'https://example.com/users/test-user11#pubkey', 'https://example.com/users/test-user11/following', + 'https://example.com/users/test-user11/followers', 0, false, 0, 0, 0, null); + +insert into POSTS (id, actor_id, overview, content, text, created_at, visibility, url, repost_id, reply_id, sensitive, + ap_id, + deleted) +VALUES (1242, 11, null, '

test post

', 'test post', 12345680, 0, + 'https://example.com/users/test-user11/posts/1242', null, null, false, + 'https://example.com/users/test-user11/posts/1242', false); + +insert into MEDIA (ID, NAME, URL, REMOTE_URL, THUMBNAIL_URL, TYPE, BLURHASH, MIME_TYPE, DESCRIPTION) +VALUES (1, 'test-media', 'https://example.com/media/test-media.png', null, null, 0, null, 'image/png', null), + (2, 'test-media2', 'https://example.com/media/test-media2.png', null, null, 0, null, 'image/png', null); + +insert into POSTS_MEDIA(POST_ID, MEDIA_ID) +VALUES (1242, 1), + (1242, 2); diff --git a/hideout-mastodon/src/test/resources/sql/note/リプライになっている投稿はinReplyToが存在する.sql b/hideout-mastodon/src/test/resources/sql/note/リプライになっている投稿はinReplyToが存在する.sql new file mode 100644 index 00000000..41ac73a4 --- /dev/null +++ b/hideout-mastodon/src/test/resources/sql/note/リプライになっている投稿はinReplyToが存在する.sql @@ -0,0 +1,20 @@ +insert into "actors" (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, + created_at, key_id, following, followers, instance, locked, following_count, followers_count, + posts_count, last_post_at) +VALUES (10, 'test-user10', 'example.com', 'Im test-user10.', 'THis account is test-user10.', + 'https://example.com/users/test-user10/inbox', + 'https://example.com/users/test-user10/outbox', 'https://example.com/users/test-user10', + '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', + '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, + 'https://example.com/users/test-user10#pubkey', 'https://example.com/users/test-user10/following', + 'https://example.com/users/test-user10/followers', 0, false, 0, 0, 0, null); + +insert into POSTS (id, actor_id, overview, content, text, created_at, visibility, url, repost_id, reply_id, sensitive, + ap_id, + deleted) +VALUES (1240, 10, null, '

test post

', 'test post', 12345680, 0, + 'https://example.com/users/test-user10/posts/1240', null, null, false, + 'https://example.com/users/test-user10/posts/1240', false), + (1241, 10, null, '

test post

', 'test post', 12345680, 0, + 'https://example.com/users/test-user10/posts/1241', null, 1240, false, + 'https://example.com/users/test-user10/posts/1241', false); diff --git a/hideout-mastodon/src/test/resources/sql/note/匿名でfollowers投稿を取得しようとすると404.sql b/hideout-mastodon/src/test/resources/sql/note/匿名でfollowers投稿を取得しようとすると404.sql new file mode 100644 index 00000000..250cfb5a --- /dev/null +++ b/hideout-mastodon/src/test/resources/sql/note/匿名でfollowers投稿を取得しようとすると404.sql @@ -0,0 +1,17 @@ +insert into "actors" (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, + created_at, key_id, following, followers, instance, locked, following_count, followers_count, + posts_count, last_post_at) +VALUES (3, 'test-user3', 'example.com', 'Im test user3.', 'THis account is test user3.', + 'https://example.com/users/test-user3/inbox', + 'https://example.com/users/test-user3/outbox', 'https://example.com/users/test-user3', + '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', + '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, + 'https://example.com/users/test-user3#pubkey', 'https://example.com/users/test-user3/following', + 'https://example.com/users/test-user3/followers', 0, false, 0, 0, 0, null); + +insert into POSTS (id, actor_id, overview, content, text, created_at, visibility, url, repost_id, reply_id, sensitive, + ap_id, + deleted) +VALUES (1236, 3, null, '

test post

', 'test post', 12345680, 2, 'https://example.com/users/test-user3/posts/1236', + null, null, false, + 'https://example.com/users/test-user3/posts/1236', false) diff --git a/hideout-mastodon/src/test/resources/sql/note/匿名でpublic投稿を取得できる.sql b/hideout-mastodon/src/test/resources/sql/note/匿名でpublic投稿を取得できる.sql new file mode 100644 index 00000000..777f9244 --- /dev/null +++ b/hideout-mastodon/src/test/resources/sql/note/匿名でpublic投稿を取得できる.sql @@ -0,0 +1,17 @@ +insert into actors (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, created_at, + key_id, following, followers, instance, locked, following_count, followers_count, posts_count, + last_post_at) +VALUES (1, 'test-user', 'example.com', 'Im test user.', 'THis account is test user.', + 'https://example.com/users/test-user/inbox', + 'https://example.com/users/test-user/outbox', 'https://example.com/users/test-user', + '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', + '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, + 'https://example.com/users/test-user#pubkey', 'https://example.com/users/test-user/following', + 'https://example.com/users/test-users/followers', 0, false, 0, 0, 0, null); + +insert into POSTS (id, actor_id, overview, content, text, created_at, visibility, url, repost_id, reply_id, sensitive, + ap_id, + deleted) +VALUES (1234, 1, null, '

test post

', 'test post', 12345680, 0, 'https://example.com/users/test-user/posts/1234', + null, null, false, + 'https://example.com/users/test-user/posts/1234', false) diff --git a/hideout-mastodon/src/test/resources/sql/note/匿名でunlisted投稿を取得できる.sql b/hideout-mastodon/src/test/resources/sql/note/匿名でunlisted投稿を取得できる.sql new file mode 100644 index 00000000..b132734d --- /dev/null +++ b/hideout-mastodon/src/test/resources/sql/note/匿名でunlisted投稿を取得できる.sql @@ -0,0 +1,17 @@ +insert into actors (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, created_at, + key_id, following, followers, instance, locked, following_count, followers_count, posts_count, + last_post_at) +VALUES (2, 'test-user2', 'example.com', 'Im test user2.', 'THis account is test user2.', + 'https://example.com/users/test-user2/inbox', + 'https://example.com/users/test-user2/outbox', 'https://example.com/users/test-user2', + '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', + '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, + 'https://example.com/users/test-user2#pubkey', 'https://example.com/users/test-user2/following', + 'https://example.com/users/test-user2/followers', 0, false, 0, 0, 0, null); + +insert into POSTS (id, actor_id, overview, content, text, created_at, visibility, url, repost_id, reply_id, sensitive, + ap_id, + deleted) +VALUES (1235, 2, null, '

test post

', 'test post', 12345680, 1, 'https://example.com/users/test-user2/posts/1235', + null, null, false, + 'https://example.com/users/test-user2/posts/1235', false) diff --git a/hideout-mastodon/src/test/resources/sql/notification/test-mastodon_notifications.sql b/hideout-mastodon/src/test/resources/sql/notification/test-mastodon_notifications.sql new file mode 100644 index 00000000..c97a25a7 --- /dev/null +++ b/hideout-mastodon/src/test/resources/sql/notification/test-mastodon_notifications.sql @@ -0,0 +1,66 @@ +insert into mastodon_notifications (id, user_id, type, created_at, account_id, status_id, report_id, relationship_serverance_event_id) +values (1, 1, 'follow', current_timestamp, 2, null, null, null), + (2, 1, 'follow', current_timestamp, 2, null, null, null), + (3, 1, 'follow', current_timestamp, 2, null, null, null), + (4, 1, 'follow', current_timestamp, 2, null, null, null), + (5, 1, 'follow', current_timestamp, 2, null, null, null), + (6, 1, 'follow', current_timestamp, 2, null, null, null), + (7, 1, 'follow', current_timestamp, 2, null, null, null), + (8, 1, 'follow', current_timestamp, 2, null, null, null), + (9, 1, 'follow', current_timestamp, 2, null, null, null), + (10, 1, 'follow', current_timestamp, 2, null, null, null), + (11, 1, 'follow', current_timestamp, 2, null, null, null), + (12, 1, 'follow', current_timestamp, 2, null, null, null), + (13, 1, 'follow', current_timestamp, 2, null, null, null), + (14, 1, 'follow', current_timestamp, 2, null, null, null), + (15, 1, 'follow', current_timestamp, 2, null, null, null), + (16, 1, 'follow', current_timestamp, 2, null, null, null), + (17, 1, 'follow', current_timestamp, 2, null, null, null), + (18, 1, 'follow', current_timestamp, 2, null, null, null), + (19, 1, 'follow', current_timestamp, 2, null, null, null), + (20, 1, 'follow', current_timestamp, 2, null, null, null), + (21, 1, 'follow', current_timestamp, 2, null, null, null), + (22, 1, 'follow', current_timestamp, 2, null, null, null), + (23, 1, 'follow', current_timestamp, 2, null, null, null), + (24, 1, 'follow', current_timestamp, 2, null, null, null), + (25, 1, 'follow', current_timestamp, 2, null, null, null), + (26, 1, 'follow', current_timestamp, 2, null, null, null), + (27, 1, 'follow', current_timestamp, 2, null, null, null), + (28, 1, 'follow', current_timestamp, 2, null, null, null), + (29, 1, 'follow', current_timestamp, 2, null, null, null), + (30, 1, 'follow', current_timestamp, 2, null, null, null), + (31, 1, 'follow', current_timestamp, 2, null, null, null), + (32, 1, 'follow', current_timestamp, 2, null, null, null), + (33, 1, 'follow', current_timestamp, 2, null, null, null), + (34, 1, 'follow', current_timestamp, 2, null, null, null), + (35, 1, 'follow', current_timestamp, 2, null, null, null), + (36, 1, 'follow', current_timestamp, 2, null, null, null), + (37, 1, 'follow', current_timestamp, 2, null, null, null), + (38, 1, 'follow', current_timestamp, 2, null, null, null), + (39, 1, 'follow', current_timestamp, 2, null, null, null), + (40, 1, 'follow', current_timestamp, 2, null, null, null), + (41, 1, 'follow', current_timestamp, 2, null, null, null), + (42, 1, 'follow', current_timestamp, 2, null, null, null), + (43, 1, 'follow', current_timestamp, 2, null, null, null), + (44, 1, 'follow', current_timestamp, 2, null, null, null), + (45, 1, 'follow', current_timestamp, 2, null, null, null), + (46, 1, 'follow', current_timestamp, 2, null, null, null), + (47, 1, 'follow', current_timestamp, 2, null, null, null), + (48, 1, 'follow', current_timestamp, 2, null, null, null), + (49, 1, 'follow', current_timestamp, 2, null, null, null), + (50, 1, 'follow', current_timestamp, 2, null, null, null), + (51, 1, 'follow', current_timestamp, 2, null, null, null), + (52, 1, 'follow', current_timestamp, 2, null, null, null), + (53, 1, 'follow', current_timestamp, 2, null, null, null), + (54, 1, 'follow', current_timestamp, 2, null, null, null), + (55, 1, 'follow', current_timestamp, 2, null, null, null), + (56, 1, 'follow', current_timestamp, 2, null, null, null), + (57, 1, 'follow', current_timestamp, 2, null, null, null), + (58, 1, 'follow', current_timestamp, 2, null, null, null), + (59, 1, 'follow', current_timestamp, 2, null, null, null), + (60, 1, 'follow', current_timestamp, 2, null, null, null), + (61, 1, 'follow', current_timestamp, 2, null, null, null), + (62, 1, 'follow', current_timestamp, 2, null, null, null), + (63, 1, 'follow', current_timestamp, 2, null, null, null), + (64, 1, 'follow', current_timestamp, 2, null, null, null), + (65, 1, 'follow', current_timestamp, 2, null, null, null); \ No newline at end of file diff --git a/hideout-mastodon/src/test/resources/sql/notification/test-notifications.sql b/hideout-mastodon/src/test/resources/sql/notification/test-notifications.sql new file mode 100644 index 00000000..38982603 --- /dev/null +++ b/hideout-mastodon/src/test/resources/sql/notification/test-notifications.sql @@ -0,0 +1,66 @@ +insert into notifications(id, type, user_id, source_actor_id, post_id, text, reaction_id, created_at) +VALUES (1, 'follow', 1, 2, null, null, null, current_timestamp), + (2, 'follow', 1, 2, null, null, null, current_timestamp), + (3, 'follow', 1, 2, null, null, null, current_timestamp), + (4, 'follow', 1, 2, null, null, null, current_timestamp), + (5, 'follow', 1, 2, null, null, null, current_timestamp), + (6, 'follow', 1, 2, null, null, null, current_timestamp), + (7, 'follow', 1, 2, null, null, null, current_timestamp), + (8, 'follow', 1, 2, null, null, null, current_timestamp), + (9, 'follow', 1, 2, null, null, null, current_timestamp), + (10, 'follow', 1, 2, null, null, null, current_timestamp), + (11, 'follow', 1, 2, null, null, null, current_timestamp), + (12, 'follow', 1, 2, null, null, null, current_timestamp), + (13, 'follow', 1, 2, null, null, null, current_timestamp), + (14, 'follow', 1, 2, null, null, null, current_timestamp), + (15, 'follow', 1, 2, null, null, null, current_timestamp), + (16, 'follow', 1, 2, null, null, null, current_timestamp), + (17, 'follow', 1, 2, null, null, null, current_timestamp), + (18, 'follow', 1, 2, null, null, null, current_timestamp), + (19, 'follow', 1, 2, null, null, null, current_timestamp), + (20, 'follow', 1, 2, null, null, null, current_timestamp), + (21, 'follow', 1, 2, null, null, null, current_timestamp), + (22, 'follow', 1, 2, null, null, null, current_timestamp), + (23, 'follow', 1, 2, null, null, null, current_timestamp), + (24, 'follow', 1, 2, null, null, null, current_timestamp), + (25, 'follow', 1, 2, null, null, null, current_timestamp), + (26, 'follow', 1, 2, null, null, null, current_timestamp), + (27, 'follow', 1, 2, null, null, null, current_timestamp), + (28, 'follow', 1, 2, null, null, null, current_timestamp), + (29, 'follow', 1, 2, null, null, null, current_timestamp), + (30, 'follow', 1, 2, null, null, null, current_timestamp), + (31, 'follow', 1, 2, null, null, null, current_timestamp), + (32, 'follow', 1, 2, null, null, null, current_timestamp), + (33, 'follow', 1, 2, null, null, null, current_timestamp), + (34, 'follow', 1, 2, null, null, null, current_timestamp), + (35, 'follow', 1, 2, null, null, null, current_timestamp), + (36, 'follow', 1, 2, null, null, null, current_timestamp), + (37, 'follow', 1, 2, null, null, null, current_timestamp), + (38, 'follow', 1, 2, null, null, null, current_timestamp), + (39, 'follow', 1, 2, null, null, null, current_timestamp), + (40, 'follow', 1, 2, null, null, null, current_timestamp), + (41, 'follow', 1, 2, null, null, null, current_timestamp), + (42, 'follow', 1, 2, null, null, null, current_timestamp), + (43, 'follow', 1, 2, null, null, null, current_timestamp), + (44, 'follow', 1, 2, null, null, null, current_timestamp), + (45, 'follow', 1, 2, null, null, null, current_timestamp), + (46, 'follow', 1, 2, null, null, null, current_timestamp), + (47, 'follow', 1, 2, null, null, null, current_timestamp), + (48, 'follow', 1, 2, null, null, null, current_timestamp), + (49, 'follow', 1, 2, null, null, null, current_timestamp), + (50, 'follow', 1, 2, null, null, null, current_timestamp), + (51, 'follow', 1, 2, null, null, null, current_timestamp), + (52, 'follow', 1, 2, null, null, null, current_timestamp), + (53, 'follow', 1, 2, null, null, null, current_timestamp), + (54, 'follow', 1, 2, null, null, null, current_timestamp), + (55, 'follow', 1, 2, null, null, null, current_timestamp), + (56, 'follow', 1, 2, null, null, null, current_timestamp), + (57, 'follow', 1, 2, null, null, null, current_timestamp), + (58, 'follow', 1, 2, null, null, null, current_timestamp), + (59, 'follow', 1, 2, null, null, null, current_timestamp), + (60, 'follow', 1, 2, null, null, null, current_timestamp), + (61, 'follow', 1, 2, null, null, null, current_timestamp), + (62, 'follow', 1, 2, null, null, null, current_timestamp), + (63, 'follow', 1, 2, null, null, null, current_timestamp), + (64, 'follow', 1, 2, null, null, null, current_timestamp), + (65, 'follow', 1, 2, null, null, null, current_timestamp); \ No newline at end of file diff --git a/hideout-mastodon/src/test/resources/sql/posts.sql b/hideout-mastodon/src/test/resources/sql/posts.sql index f8dec6b9..cf8c71bf 100644 --- a/hideout-mastodon/src/test/resources/sql/posts.sql +++ b/hideout-mastodon/src/test/resources/sql/posts.sql @@ -1,15 +1,15 @@ insert into posts (id, actor_id, instance_id, overview, content, text, created_at, visibility, url, repost_id, reply_id, sensitive, ap_id, deleted, hide, move_to) values (1, 1, 1, null, 'content', 'text', current_timestamp, 'PUBLIC', 'https://example.com', null, null, false, - 'https://example.com', false, false, null), + 'https://example.com/1', false, false, null), (2, 2, 2, null, 'content', 'text', current_timestamp, 'FOLLOWERS', 'https://example.com', null, null, false, - 'https://example.com', false, false, null), + 'https://example.com/2', false, false, null), (3, 3, 1, null, 'content', 'text', current_timestamp, 'PUBLIC', 'https://example.com', null, null, false, - 'https://example.com', false, false, null), + 'https://example.com/3', false, false, null), (4, 4, 1, null, 'content', 'text', current_timestamp, 'FOLLOWERS', 'https://example.com', null, null, false, - 'https://example.com', false, false, null), + 'https://example.com/4', false, false, null), (5, 4, 1, null, 'content', 'text', current_timestamp, 'DIRECT', 'https://example.com', null, null, false, - 'https://example.com', false, false, null); + 'https://example.com/5', false, false, null); insert into posts_visible_actors(post_id, actor_id) VALUES (5, 2); \ No newline at end of file diff --git a/hideout-mastodon/src/test/resources/sql/test-custom-emoji.sql b/hideout-mastodon/src/test/resources/sql/test-custom-emoji.sql new file mode 100644 index 00000000..83c2747a --- /dev/null +++ b/hideout-mastodon/src/test/resources/sql/test-custom-emoji.sql @@ -0,0 +1,3 @@ +insert into emojis(id, name, domain, instance_id, url, category, created_at) +VALUES (1, 'kotlin', 'example.com', null, 'https://example.com/emojis/kotlin', null, + TIMESTAMP '2024-01-08 07:51:30.036Z'); diff --git a/hideout-mastodon/src/test/resources/sql/test-post.sql b/hideout-mastodon/src/test/resources/sql/test-post.sql new file mode 100644 index 00000000..da7c5343 --- /dev/null +++ b/hideout-mastodon/src/test/resources/sql/test-post.sql @@ -0,0 +1,4 @@ +insert into posts (id, actor_id, instance_id, overview, content, text, created_at, visibility, url, repost_id, reply_id, + sensitive, ap_id, deleted, hide, move_to) +VALUES (1, 1, 0, null,'

test post

', 'hello', 1234455, 0, 'https://localhost/users/1/posts/1', null, null, false, + 'https://users/1/posts/1'); diff --git a/hideout-mastodon/src/test/resources/sql/test-user.sql b/hideout-mastodon/src/test/resources/sql/test-user.sql new file mode 100644 index 00000000..728af6f5 --- /dev/null +++ b/hideout-mastodon/src/test/resources/sql/test-user.sql @@ -0,0 +1,11 @@ +insert into "actors" (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, + created_at, key_id, following, followers, instance, locked, following_count, followers_count, + posts_count, last_post_at) +VALUES (1, 'test-user', 'example.com', 'Im test user.', 'THis account is test user.', + 'https://example.com/users/test-user/inbox', + 'https://example.com/users/test-user/outbox', 'https://example.com/users/test-user', + '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', + '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, + 'https://example.com/users/test-user#pubkey', 'https://example.com/users/test-user/following', + 'https://example.com/users/test-users/followers', 0, false, 0, 0, 0, null); + diff --git a/hideout-mastodon/src/test/resources/sql/test-user2.sql b/hideout-mastodon/src/test/resources/sql/test-user2.sql new file mode 100644 index 00000000..0f736704 --- /dev/null +++ b/hideout-mastodon/src/test/resources/sql/test-user2.sql @@ -0,0 +1,10 @@ +insert into "actors" (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, + created_at, key_id, following, followers, instance, locked, following_count, followers_count, + posts_count, last_post_at) +VALUES (2, 'test-user2', 'example.com', 'Im test user.', 'THis account is test user.', + 'https://example.com/users/test-user2/inbox', + 'https://example.com/users/test-user2/outbox', 'https://example.com/users/test-user2', + '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', + '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, + 'https://example.com/users/test-user2#pubkey', 'https://example.com/users/test-user2/following', + 'https://example.com/users/test-user2s/followers', 0, false, 0, 0, 0, null); diff --git a/hideout-mastodon/src/test/resources/sql/userdetail.sql b/hideout-mastodon/src/test/resources/sql/userdetail.sql new file mode 100644 index 00000000..c0b3ee2d --- /dev/null +++ b/hideout-mastodon/src/test/resources/sql/userdetail.sql @@ -0,0 +1,2 @@ +insert into user_details(id, actor_id, password, auto_accept_followee_follow_request, last_migration, home_timeline_id) +VALUES (1, 1, 'veeeeeeeeeeryStrongPassword', false, null, null); \ No newline at end of file diff --git a/libs.versions.toml b/libs.versions.toml index 1f560b91..a20a8b08 100644 --- a/libs.versions.toml +++ b/libs.versions.toml @@ -124,7 +124,7 @@ grpc-kotlin = ["grpc-kotlin-stub", "grpc-netty", "grpc-protobuf", "protobuf-kotl [plugins] kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } -spring-boot = { id = "org.springframework.boot", version = "3.3.3" } +spring-boot = { id = "org.springframework.boot", version = "3.3.4" } detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } kotlin-spring = { id = "org.jetbrains.kotlin.plugin.spring", version.ref = "kotlin" } kover = { id = "org.jetbrains.kotlinx.kover", version = "0.8.3" } diff --git a/owl/build.gradle.kts b/owl/build.gradle.kts index fddf3a79..adff1087 100644 --- a/owl/build.gradle.kts +++ b/owl/build.gradle.kts @@ -1,6 +1,10 @@ +import kotlinx.kover.gradle.plugin.dsl.CoverageUnit + plugins { alias(libs.plugins.kotlin.jvm) id("maven-publish") + alias(libs.plugins.kover) + alias(libs.plugins.detekt) } @@ -27,6 +31,8 @@ subprojects { apply { plugin("org.jetbrains.kotlin.jvm") plugin("maven-publish") + plugin(rootProject.libs.plugins.kover.get().pluginId) + plugin(rootProject.libs.plugins.detekt.get().pluginId) } kotlin { jvmToolchain(21) @@ -35,12 +41,42 @@ subprojects { dependencies { implementation("org.slf4j:slf4j-api:2.0.15") testImplementation("org.junit.jupiter:junit-jupiter:5.10.3") - + detektPlugins(rootProject.libs.detekt.formatting) } - tasks.test { - useJUnitPlatform() + detekt { + parallel = true + config.setFrom(files("$rootDir/../detekt.yml")) + buildUponDefaultConfig = true + basePath = "${projectDir}/src/main/kotlin" + autoCorrect = true + } + + project.gradle.taskGraph.whenReady { + if (this.hasTask(":koverGenerateArtifact")) { + val task = this.allTasks.find { it.name == "test" } + val verificationTask = task as VerificationTask + verificationTask.ignoreFailures = true + } + } + tasks { + withType { + exclude("**/generated/**") + setSource("src/main/kotlin") + exclude("build/") + configureEach { + exclude("**/org/koin/ksp/generated/**", "**/generated/**") + } + } + withType() { + configureEach { + exclude("**/org/koin/ksp/generated/**", "**/generated/**") + } + } + withType { + useJUnitPlatform() + } } publishing { @@ -69,4 +105,63 @@ subprojects { } } } -} \ No newline at end of file +} + +dependencies { + kover(project(":owl-broker")) + kover(project(":owl-broker:owl-broker-mongodb")) + kover(project(":owl-common")) + kover(project(":owl-common:owl-common-serialize-jackson")) + kover(project(":owl-consumer")) + kover(project(":owl-producer")) + kover(project(":owl-producer:owl-producer-api")) + kover(project(":owl-producer:owl-producer-default")) + kover(project(":owl-producer:owl-producer-embedded")) +} + +detekt { + parallel = true + config.setFrom(files("../detekt.yml")) + buildUponDefaultConfig = true + basePath = "${projectDir}/src/main/kotlin" + autoCorrect = true +} + +project.gradle.taskGraph.whenReady { + if (this.hasTask(":koverGenerateArtifact")) { + val task = this.allTasks.find { it.name == "test" } + val verificationTask = task as VerificationTask + verificationTask.ignoreFailures = true + } +} + +kover { + currentProject { + sources { + excludedSourceSets.addAll("grpc", "grpckt") + } + } + reports { + verify { + rule { + bound { + minValue = 50 + coverageUnits = CoverageUnit.INSTRUCTION + } + } + } + total { + xml { + title = "Owl" + xmlFile = file("$buildDir/reports/kover/owl.xml") + } + filters { + excludes { + packages("dev.usbharu.owl.generated") + } + } + } + + + } +} diff --git a/owl/gradle/wrapper/gradle-wrapper.properties b/owl/gradle/wrapper/gradle-wrapper.properties index 0aaefbca..9355b415 100644 --- a/owl/gradle/wrapper/gradle-wrapper.properties +++ b/owl/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/owl/owl-broker/build.gradle.kts b/owl/owl-broker/build.gradle.kts index 8c055cfc..42b90f13 100644 --- a/owl/owl-broker/build.gradle.kts +++ b/owl/owl-broker/build.gradle.kts @@ -35,7 +35,7 @@ protobuf { artifact = libs.protoc.gen.grpc.java.get().toString() } create("grpckt") { - artifact = libs.protoc.gen.grpc.kotlin.get().toString() + "jdk8@jar" + artifact = libs.protoc.gen.grpc.kotlin.get().toString() + ":jdk8@jar" } } generateProtoTasks { diff --git a/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongoModuleContext.kt b/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongoModuleContext.kt index 5c1af4fb..472207ea 100644 --- a/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongoModuleContext.kt +++ b/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongoModuleContext.kt @@ -32,7 +32,6 @@ import org.koin.dsl.module class MongoModuleContext : ModuleContext { override fun module(): Module { - return module { single { val clientSettings = @@ -47,7 +46,6 @@ class MongoModuleContext : ModuleContext { ) .uuidRepresentation(UuidRepresentation.STANDARD).build() - MongoClient.create(clientSettings) .getDatabase(System.getProperty("owl.broker.mongo.database", "mongo-test")) } @@ -59,4 +57,4 @@ class MongoModuleContext : ModuleContext { single { MongodbTaskResultRepository(get(), get()) } } } -} \ No newline at end of file +} diff --git a/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbConsumerRepository.kt b/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbConsumerRepository.kt index 4310b956..3d4eef8d 100644 --- a/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbConsumerRepository.kt +++ b/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbConsumerRepository.kt @@ -33,7 +33,11 @@ class MongodbConsumerRepository(database: MongoDatabase) : ConsumerRepository { private val collection = database.getCollection("consumers") override suspend fun save(consumer: Consumer): Consumer = withContext(Dispatchers.IO) { - collection.replaceOne(Filters.eq("_id", consumer.id.toString()), ConsumerMongodb.of(consumer), ReplaceOptions().upsert(true)) + collection.replaceOne( + Filters.eq("_id", consumer.id.toString()), + ConsumerMongodb.of(consumer), + ReplaceOptions().upsert(true) + ) return@withContext consumer } @@ -49,15 +53,19 @@ data class ConsumerMongodb( val name: String, val hostname: String, val tasks: List -){ +) { - fun toConsumer():Consumer{ + fun toConsumer(): Consumer { return Consumer( - UUID.fromString(id), name, hostname, tasks + UUID.fromString(id), + name, + hostname, + tasks ) } - companion object{ - fun of(consumer: Consumer):ConsumerMongodb{ + + companion object { + fun of(consumer: Consumer): ConsumerMongodb { return ConsumerMongodb( consumer.id.toString(), consumer.name, @@ -66,4 +74,4 @@ data class ConsumerMongodb( ) } } -} \ No newline at end of file +} diff --git a/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbProducerRepository.kt b/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbProducerRepository.kt index 76d9a755..6fba0352 100644 --- a/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbProducerRepository.kt +++ b/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbProducerRepository.kt @@ -68,4 +68,4 @@ data class ProducerMongodb( ) } } -} \ No newline at end of file +} diff --git a/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbQueuedTaskRepository.kt b/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbQueuedTaskRepository.kt index 833baca9..720ac9dc 100644 --- a/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbQueuedTaskRepository.kt +++ b/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbQueuedTaskRepository.kt @@ -48,7 +48,8 @@ class MongodbQueuedTaskRepository( override suspend fun save(queuedTask: QueuedTask): QueuedTask { withContext(Dispatchers.IO) { collection.replaceOne( - eq("_id", queuedTask.task.id.toString()), QueuedTaskMongodb.of(propertySerializerFactory, queuedTask), + eq("_id", queuedTask.task.id.toString()), + QueuedTaskMongodb.of(propertySerializerFactory, queuedTask), ReplaceOptions().upsert(true) ) } @@ -57,7 +58,6 @@ class MongodbQueuedTaskRepository( override suspend fun findByTaskIdAndAssignedConsumerIsNullAndUpdate(id: UUID, update: QueuedTask): QueuedTask { return withContext(Dispatchers.IO) { - val findOneAndUpdate = collection.findOneAndUpdate( and( eq("_id", id.toString()), @@ -108,7 +108,7 @@ data class QueuedTaskMongodb( val task: TaskMongodb, val attempt: Int, val queuedAt: Instant, - val priority:Int, + val priority: Int, val isActive: Boolean, val timeoutAt: Instant?, val assignedConsumer: String?, @@ -155,14 +155,14 @@ data class QueuedTaskMongodb( companion object { fun of(propertySerializerFactory: PropertySerializerFactory, task: Task): TaskMongodb { return TaskMongodb( - task.name, - task.id.toString(), - task.publishProducerId.toString(), - task.publishedAt, - task.nextRetry, - task.completedAt, - task.attempt, - PropertySerializeUtils.serialize(propertySerializerFactory, task.properties) + name = task.name, + id = task.id.toString(), + publishProducerId = task.publishProducerId.toString(), + publishedAt = task.publishedAt, + nextRetry = task.nextRetry, + completedAt = task.completedAt, + attempt = task.attempt, + properties = PropertySerializeUtils.serialize(propertySerializerFactory, task.properties) ) } } @@ -171,16 +171,16 @@ data class QueuedTaskMongodb( companion object { fun of(propertySerializerFactory: PropertySerializerFactory, queuedTask: QueuedTask): QueuedTaskMongodb { return QueuedTaskMongodb( - queuedTask.task.id.toString(), - TaskMongodb.of(propertySerializerFactory, queuedTask.task), - queuedTask.attempt, - queuedTask.queuedAt, - queuedTask.priority, - queuedTask.isActive, - queuedTask.timeoutAt, - queuedTask.assignedConsumer?.toString(), - queuedTask.assignedAt + id = queuedTask.task.id.toString(), + task = TaskMongodb.of(propertySerializerFactory, queuedTask.task), + attempt = queuedTask.attempt, + queuedAt = queuedTask.queuedAt, + priority = queuedTask.priority, + isActive = queuedTask.isActive, + timeoutAt = queuedTask.timeoutAt, + assignedConsumer = queuedTask.assignedConsumer?.toString(), + assignedAt = queuedTask.assignedAt ) } } -} \ No newline at end of file +} diff --git a/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbTaskDefinitionRepository.kt b/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbTaskDefinitionRepository.kt index f3b384a1..e51177cc 100644 --- a/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbTaskDefinitionRepository.kt +++ b/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbTaskDefinitionRepository.kt @@ -41,7 +41,7 @@ class MongodbTaskDefinitionRepository(database: MongoDatabase) : TaskDefinitionR } override suspend fun deleteByName(name: String): Unit = withContext(Dispatchers.IO) { - collection.deleteOne(Filters.eq("_id",name)) + collection.deleteOne(Filters.eq("_id", name)) } override suspend fun findByName(name: String): TaskDefinition? = withContext(Dispatchers.IO) { @@ -82,4 +82,4 @@ data class TaskDefinitionMongodb( ) } } -} \ No newline at end of file +} diff --git a/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbTaskRepository.kt b/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbTaskRepository.kt index 2745b0cd..e0299652 100644 --- a/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbTaskRepository.kt +++ b/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbTaskRepository.kt @@ -36,27 +36,29 @@ import org.bson.codecs.pojo.annotations.BsonRepresentation import java.time.Instant import java.util.* - class MongodbTaskRepository(database: MongoDatabase, private val propertySerializerFactory: PropertySerializerFactory) : TaskRepository { private val collection = database.getCollection("tasks") override suspend fun save(task: Task): Task = withContext(Dispatchers.IO) { collection.replaceOne( - Filters.eq("_id", task.id.toString()), TaskMongodb.of(propertySerializerFactory, task), + Filters.eq("_id", task.id.toString()), + TaskMongodb.of(propertySerializerFactory, task), ReplaceOptions().upsert(true) ) return@withContext task } override suspend fun saveAll(tasks: List): Unit = withContext(Dispatchers.IO) { - collection.bulkWrite(tasks.map { - ReplaceOneModel( - Filters.eq(it.id.toString()), - TaskMongodb.of(propertySerializerFactory, it), - ReplaceOptions().upsert(true) - ) - }) + collection.bulkWrite( + tasks.map { + ReplaceOneModel( + Filters.eq(it.id.toString()), + TaskMongodb.of(propertySerializerFactory, it), + ReplaceOptions().upsert(true) + ) + } + ) } override fun findByNextRetryBeforeAndCompletedAtIsNull(timestamp: Instant): Flow { @@ -75,12 +77,13 @@ class MongodbTaskRepository(database: MongoDatabase, private val propertySeriali override suspend fun findByIdAndUpdate(id: UUID, task: Task) { collection.replaceOne( - Filters.eq("_id", task.id.toString()), TaskMongodb.of(propertySerializerFactory, task), + Filters.eq("_id", task.id.toString()), + TaskMongodb.of(propertySerializerFactory, task), ReplaceOptions().upsert(false) ) } - override suspend fun findByPublishProducerIdAndCompletedAtIsNotNull(publishProducerId: UUID): Flow { + override fun findByPublishProducerIdAndCompletedAtIsNotNull(publishProducerId: UUID): Flow { return collection .find(Filters.eq(TaskMongodb::publishProducerId.name, publishProducerId.toString())) .map { it.toTask(propertySerializerFactory) } @@ -116,15 +119,15 @@ data class TaskMongodb( companion object { fun of(propertySerializerFactory: PropertySerializerFactory, task: Task): TaskMongodb { return TaskMongodb( - task.name, - task.id.toString(), - task.publishProducerId.toString(), - task.publishedAt, - task.nextRetry, - task.completedAt, - task.attempt, - PropertySerializeUtils.serialize(propertySerializerFactory, task.properties) + name = task.name, + id = task.id.toString(), + publishProducerId = task.publishProducerId.toString(), + publishedAt = task.publishedAt, + nextRetry = task.nextRetry, + completedAt = task.completedAt, + attempt = task.attempt, + properties = PropertySerializeUtils.serialize(propertySerializerFactory, task.properties) ) } } -} \ No newline at end of file +} diff --git a/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbTaskResultRepository.kt b/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbTaskResultRepository.kt index 2336a45c..cc671a65 100644 --- a/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbTaskResultRepository.kt +++ b/owl/owl-broker/owl-broker-mongodb/src/main/kotlin/dev/usbharu/owl/broker/mongodb/MongodbTaskResultRepository.kt @@ -41,14 +41,17 @@ class MongodbTaskResultRepository( private val collection = database.getCollection("task_results") override suspend fun save(taskResult: TaskResult): TaskResult = withContext(Dispatchers.IO) { collection.replaceOne( - Filters.eq(taskResult.id.toString()), TaskResultMongodb.of(propertySerializerFactory, taskResult), + Filters.eq(taskResult.id.toString()), + TaskResultMongodb.of(propertySerializerFactory, taskResult), ReplaceOptions().upsert(true) ) return@withContext taskResult } override fun findByTaskId(id: UUID): Flow { - return collection.find(Filters.eq(id.toString())).map { it.toTaskResult(propertySerializerFactory) }.flowOn(Dispatchers.IO) + return collection.find( + Filters.eq(id.toString()) + ).map { it.toTaskResult(propertySerializerFactory) }.flowOn(Dispatchers.IO) } } @@ -65,27 +68,25 @@ data class TaskResultMongodb( fun toTaskResult(propertySerializerFactory: PropertySerializerFactory): TaskResult { return TaskResult( - UUID.fromString(id), - UUID.fromString(taskId), - success, - attempt, - PropertySerializeUtils.deserialize(propertySerializerFactory, result), - message + id = UUID.fromString(id), + taskId = UUID.fromString(taskId), + success = success, + attempt = attempt, + result = PropertySerializeUtils.deserialize(propertySerializerFactory, result), + message = message ) } companion object { fun of(propertySerializerFactory: PropertySerializerFactory, taskResult: TaskResult): TaskResultMongodb { return TaskResultMongodb( - taskResult.id.toString(), - taskResult.taskId.toString(), - taskResult.success, - taskResult.attempt, - PropertySerializeUtils.serialize(propertySerializerFactory, taskResult.result), - taskResult.message + id = taskResult.id.toString(), + taskId = taskResult.taskId.toString(), + success = taskResult.success, + attempt = taskResult.attempt, + result = PropertySerializeUtils.serialize(propertySerializerFactory, taskResult.result), + message = taskResult.message ) - } - } -} \ No newline at end of file +} diff --git a/owl/owl-broker/owl-broker-mongodb/src/test/kotlin/dev/usbharu/owl/broker/mongodb/MongodbConsumerRepositoryTest.kt b/owl/owl-broker/owl-broker-mongodb/src/test/kotlin/dev/usbharu/owl/broker/mongodb/MongodbConsumerRepositoryTest.kt index b71c5ea4..2bd00586 100644 --- a/owl/owl-broker/owl-broker-mongodb/src/test/kotlin/dev/usbharu/owl/broker/mongodb/MongodbConsumerRepositoryTest.kt +++ b/owl/owl-broker/owl-broker-mongodb/src/test/kotlin/dev/usbharu/owl/broker/mongodb/MongodbConsumerRepositoryTest.kt @@ -7,12 +7,14 @@ import dev.usbharu.owl.broker.domain.model.consumer.Consumer import kotlinx.coroutines.runBlocking import org.bson.UuidRepresentation import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import java.util.* class MongodbConsumerRepositoryTest { @Test + @Disabled fun name() { val clientSettings = diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/Main.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/Main.kt index d5f25364..2a0ebb1f 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/Main.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/Main.kt @@ -90,7 +90,6 @@ fun main() { logger.info("Use module name: {}", moduleContext) - val koin = startKoin { printLogger() @@ -98,7 +97,6 @@ fun main() { single { DefaultRetryPolicyFactory(mapOf("" to ExponentialRetryPolicy())) } - } modules(mainModule, module, moduleContext.module()) } @@ -108,4 +106,4 @@ fun main() { runBlocking { application.start(50051).join() } -} \ No newline at end of file +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/ModuleContext.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/ModuleContext.kt index fbb32f7c..a75dffb3 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/ModuleContext.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/ModuleContext.kt @@ -19,11 +19,9 @@ package dev.usbharu.owl.broker import org.koin.core.module.Module interface ModuleContext { - fun module():Module + fun module(): Module } data object EmptyModuleContext : ModuleContext { - override fun module(): Module { - return org.koin.dsl.module { } - } -} \ No newline at end of file + override fun module(): Module = org.koin.dsl.module { } +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/OwlBrokerApplication.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/OwlBrokerApplication.kt index a67661ca..96b824ce 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/OwlBrokerApplication.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/OwlBrokerApplication.kt @@ -38,7 +38,7 @@ class OwlBrokerApplication( private lateinit var server: Server - fun start(port: Int,coroutineScope: CoroutineScope = GlobalScope):Job { + fun start(port: Int, coroutineScope: CoroutineScope = GlobalScope): Job { server = ServerBuilder.forPort(port) .addService(assignmentTaskService) .addService(definitionTaskService) @@ -64,5 +64,4 @@ class OwlBrokerApplication( fun stop() { server.shutdown() } - -} \ No newline at end of file +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/InvalidRepositoryException.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/InvalidRepositoryException.kt index 9019c314..a48232fa 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/InvalidRepositoryException.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/InvalidRepositoryException.kt @@ -27,4 +27,4 @@ class InvalidRepositoryException : RuntimeException { enableSuppression, writableStackTrace ) -} \ No newline at end of file +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/repository/FailedSaveException.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/repository/FailedSaveException.kt index 4cb68305..782ac6ab 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/repository/FailedSaveException.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/repository/FailedSaveException.kt @@ -27,4 +27,4 @@ class FailedSaveException : RuntimeException { enableSuppression, writableStackTrace ) -} \ No newline at end of file +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/repository/RecordNotFoundException.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/repository/RecordNotFoundException.kt index 62921c46..2a28a662 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/repository/RecordNotFoundException.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/repository/RecordNotFoundException.kt @@ -27,4 +27,4 @@ open class RecordNotFoundException : RuntimeException { enableSuppression, writableStackTrace ) -} \ No newline at end of file +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/service/IncompatibleTaskException.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/service/IncompatibleTaskException.kt index 9f940bc2..54fe2717 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/service/IncompatibleTaskException.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/service/IncompatibleTaskException.kt @@ -27,4 +27,4 @@ class IncompatibleTaskException : RuntimeException { enableSuppression, writableStackTrace ) -} \ No newline at end of file +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/service/QueueCannotDequeueException.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/service/QueueCannotDequeueException.kt index 49b47dbf..c89df8cc 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/service/QueueCannotDequeueException.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/service/QueueCannotDequeueException.kt @@ -27,4 +27,4 @@ class QueueCannotDequeueException : RuntimeException { enableSuppression, writableStackTrace ) -} \ No newline at end of file +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/service/TaskNotRegisterException.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/service/TaskNotRegisterException.kt index ca894165..bc134711 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/service/TaskNotRegisterException.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/exception/service/TaskNotRegisterException.kt @@ -27,4 +27,4 @@ class TaskNotRegisterException : RuntimeException { enableSuppression, writableStackTrace ) -} \ No newline at end of file +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/consumer/ConsumerRepository.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/consumer/ConsumerRepository.kt index 34ebb0af..47289625 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/consumer/ConsumerRepository.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/consumer/ConsumerRepository.kt @@ -19,7 +19,7 @@ package dev.usbharu.owl.broker.domain.model.consumer import java.util.* interface ConsumerRepository { - suspend fun save(consumer: Consumer):Consumer + suspend fun save(consumer: Consumer): Consumer - suspend fun findById(id:UUID):Consumer? -} \ No newline at end of file + suspend fun findById(id: UUID): Consumer? +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/producer/Producer.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/producer/Producer.kt index eebbafd4..4bf96d77 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/producer/Producer.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/producer/Producer.kt @@ -20,9 +20,9 @@ import java.time.Instant import java.util.* data class Producer( - val id:UUID, - val name:String, - val hostname:String, - val registeredTask:List, + val id: UUID, + val name: String, + val hostname: String, + val registeredTask: List, val createdAt: Instant ) diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/producer/ProducerRepository.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/producer/ProducerRepository.kt index 932cef10..67aff038 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/producer/ProducerRepository.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/producer/ProducerRepository.kt @@ -17,5 +17,5 @@ package dev.usbharu.owl.broker.domain.model.producer interface ProducerRepository { - suspend fun save(producer: Producer):Producer -} \ No newline at end of file + suspend fun save(producer: Producer): Producer +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/queuedtask/QueuedTaskRepository.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/queuedtask/QueuedTaskRepository.kt index 9f3c12a7..0382eff9 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/queuedtask/QueuedTaskRepository.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/queuedtask/QueuedTaskRepository.kt @@ -21,14 +21,14 @@ import java.time.Instant import java.util.* interface QueuedTaskRepository { - suspend fun save(queuedTask: QueuedTask):QueuedTask + suspend fun save(queuedTask: QueuedTask): QueuedTask /** * トランザクションの代わり */ - suspend fun findByTaskIdAndAssignedConsumerIsNullAndUpdate(id:UUID,update:QueuedTask):QueuedTask + suspend fun findByTaskIdAndAssignedConsumerIsNullAndUpdate(id: UUID, update: QueuedTask): QueuedTask fun findByTaskNameInAndIsActiveIsTrueAndOrderByPriority(tasks: List, limit: Int): Flow fun findByQueuedAtBeforeAndIsActiveIsTrue(instant: Instant): Flow -} \ No newline at end of file +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/task/Task.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/task/Task.kt index 8bf3a162..03dfb63f 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/task/Task.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/task/Task.kt @@ -24,11 +24,11 @@ import java.util.* * @param attempt 失敗を含めて試行した回数 */ data class Task( - val name:String, + val name: String, val id: UUID, - val publishProducerId:UUID, + val publishProducerId: UUID, val publishedAt: Instant, - val nextRetry:Instant, + val nextRetry: Instant, val completedAt: Instant? = null, val attempt: Int, val properties: Map> diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/task/TaskRepository.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/task/TaskRepository.kt index 009dea2d..6f6294db 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/task/TaskRepository.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/task/TaskRepository.kt @@ -21,15 +21,15 @@ import java.time.Instant import java.util.* interface TaskRepository { - suspend fun save(task: Task):Task + suspend fun save(task: Task): Task - suspend fun saveAll(tasks:List) + suspend fun saveAll(tasks: List) - fun findByNextRetryBeforeAndCompletedAtIsNull(timestamp:Instant): Flow + fun findByNextRetryBeforeAndCompletedAtIsNull(timestamp: Instant): Flow suspend fun findById(uuid: UUID): Task? - suspend fun findByIdAndUpdate(id:UUID,task: Task) + suspend fun findByIdAndUpdate(id: UUID, task: Task) - suspend fun findByPublishProducerIdAndCompletedAtIsNotNull(publishProducerId:UUID):Flow -} \ No newline at end of file + fun findByPublishProducerIdAndCompletedAtIsNotNull(publishProducerId: UUID): Flow +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskdefinition/TaskDefinition.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskdefinition/TaskDefinition.kt index 8fcb8f1e..a775d727 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskdefinition/TaskDefinition.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskdefinition/TaskDefinition.kt @@ -22,5 +22,5 @@ data class TaskDefinition( val maxRetry: Int, val timeoutMilli: Long, val propertyDefinitionHash: Long, - val retryPolicy:String + val retryPolicy: String ) diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskdefinition/TaskDefinitionRepository.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskdefinition/TaskDefinitionRepository.kt index 2a1c3c6a..185a70a5 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskdefinition/TaskDefinitionRepository.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskdefinition/TaskDefinitionRepository.kt @@ -18,7 +18,7 @@ package dev.usbharu.owl.broker.domain.model.taskdefinition interface TaskDefinitionRepository { suspend fun save(taskDefinition: TaskDefinition): TaskDefinition - suspend fun deleteByName(name:String) + suspend fun deleteByName(name: String) - suspend fun findByName(name:String):TaskDefinition? -} \ No newline at end of file + suspend fun findByName(name: String): TaskDefinition? +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskresult/TaskResult.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskresult/TaskResult.kt index b024d04c..b2480e65 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskresult/TaskResult.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskresult/TaskResult.kt @@ -21,7 +21,7 @@ import java.util.* data class TaskResult( val id: UUID, - val taskId:UUID, + val taskId: UUID, val success: Boolean, val attempt: Int, val result: Map>, diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskresult/TaskResultRepository.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskresult/TaskResultRepository.kt index 4118cfed..492b2851 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskresult/TaskResultRepository.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/domain/model/taskresult/TaskResultRepository.kt @@ -20,6 +20,6 @@ import kotlinx.coroutines.flow.Flow import java.util.* interface TaskResultRepository { - suspend fun save(taskResult: TaskResult):TaskResult - fun findByTaskId(id:UUID): Flow -} \ No newline at end of file + suspend fun save(taskResult: TaskResult): TaskResult + fun findByTaskId(id: UUID): Flow +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/external/GrpcExtension.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/external/GrpcExtension.kt index 4271b55c..601cfe5a 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/external/GrpcExtension.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/external/GrpcExtension.kt @@ -17,7 +17,7 @@ package dev.usbharu.owl.broker.external import com.google.protobuf.Timestamp -import dev.usbharu.owl.Uuid +import dev.usbharu.owl.generated.Uuid import java.time.Instant import java.util.* @@ -32,4 +32,4 @@ fun UUID.toUUID(): Uuid.UUID = Uuid fun Timestamp.toInstant(): Instant = Instant.ofEpochSecond(seconds, nanos.toLong()) -fun Instant.toTimestamp():Timestamp = Timestamp.newBuilder().setSeconds(this.epochSecond).setNanos(this.nano).build() \ No newline at end of file +fun Instant.toTimestamp(): Timestamp = Timestamp.newBuilder().setSeconds(this.epochSecond).setNanos(this.nano).build() diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/AssignmentTaskService.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/AssignmentTaskService.kt index f92faf17..f0f46d6a 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/AssignmentTaskService.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/AssignmentTaskService.kt @@ -16,14 +16,13 @@ package dev.usbharu.owl.broker.interfaces.grpc - -import dev.usbharu.owl.AssignmentTaskServiceGrpcKt -import dev.usbharu.owl.Task import dev.usbharu.owl.broker.external.toTimestamp import dev.usbharu.owl.broker.external.toUUID import dev.usbharu.owl.broker.service.QueuedTaskAssigner import dev.usbharu.owl.common.property.PropertySerializeUtils import dev.usbharu.owl.common.property.PropertySerializerFactory +import dev.usbharu.owl.generated.AssignmentTaskServiceGrpcKt +import dev.usbharu.owl.generated.Task import io.grpc.Status import io.grpc.StatusException import kotlinx.coroutines.flow.Flow @@ -33,7 +32,6 @@ import org.slf4j.LoggerFactory import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext - class AssignmentTaskService( coroutineContext: CoroutineContext = EmptyCoroutineContext, private val queuedTaskAssigner: QueuedTaskAssigner, @@ -42,7 +40,6 @@ class AssignmentTaskService( AssignmentTaskServiceGrpcKt.AssignmentTaskServiceCoroutineImplBase(coroutineContext) { override fun ready(requests: Flow): Flow { - return try { requests .flatMapMerge { @@ -72,4 +69,4 @@ class AssignmentTaskService( companion object { private val logger = LoggerFactory.getLogger(AssignmentTaskService::class.java) } -} \ No newline at end of file +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/DefinitionTaskService.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/DefinitionTaskService.kt index d00d584a..a6436405 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/DefinitionTaskService.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/DefinitionTaskService.kt @@ -17,25 +17,28 @@ package dev.usbharu.owl.broker.interfaces.grpc import com.google.protobuf.Empty -import dev.usbharu.owl.DefinitionTask -import dev.usbharu.owl.DefinitionTask.TaskDefined -import dev.usbharu.owl.DefinitionTaskServiceGrpcKt.DefinitionTaskServiceCoroutineImplBase import dev.usbharu.owl.broker.domain.model.taskdefinition.TaskDefinition import dev.usbharu.owl.broker.service.RegisterTaskService +import dev.usbharu.owl.generated.DefinitionTask +import dev.usbharu.owl.generated.DefinitionTask.TaskDefined +import dev.usbharu.owl.generated.DefinitionTaskServiceGrpcKt.DefinitionTaskServiceCoroutineImplBase import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext -class DefinitionTaskService(coroutineContext: CoroutineContext = EmptyCoroutineContext,private val registerTaskService: RegisterTaskService) : +class DefinitionTaskService( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + private val registerTaskService: RegisterTaskService +) : DefinitionTaskServiceCoroutineImplBase(coroutineContext) { override suspend fun register(request: DefinitionTask.TaskDefinition): TaskDefined { registerTaskService.registerTask( TaskDefinition( - request.name, - request.priority, - request.maxRetry, - request.timeoutMilli, - request.propertyDefinitionHash, - request.retryPolicy + name = request.name, + priority = request.priority, + maxRetry = request.maxRetry, + timeoutMilli = request.timeoutMilli, + propertyDefinitionHash = request.propertyDefinitionHash, + retryPolicy = request.retryPolicy ) ) return TaskDefined @@ -50,4 +53,4 @@ class DefinitionTaskService(coroutineContext: CoroutineContext = EmptyCoroutineC registerTaskService.unregisterTask(request.name) return Empty.getDefaultInstance() } -} \ No newline at end of file +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/ProducerService.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/ProducerService.kt index 47bbb62e..358d6827 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/ProducerService.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/ProducerService.kt @@ -16,26 +16,28 @@ package dev.usbharu.owl.broker.interfaces.grpc -import dev.usbharu.owl.ProducerOuterClass -import dev.usbharu.owl.ProducerServiceGrpcKt.ProducerServiceCoroutineImplBase import dev.usbharu.owl.broker.external.toUUID import dev.usbharu.owl.broker.service.ProducerService import dev.usbharu.owl.broker.service.RegisterProducerRequest +import dev.usbharu.owl.generated.ProducerOuterClass +import dev.usbharu.owl.generated.ProducerServiceGrpcKt.ProducerServiceCoroutineImplBase import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext - class ProducerService( coroutineContext: CoroutineContext = EmptyCoroutineContext, private val producerService: ProducerService ) : ProducerServiceCoroutineImplBase(coroutineContext) { - override suspend fun registerProducer(request: ProducerOuterClass.Producer): ProducerOuterClass.RegisterProducerResponse { + override suspend fun registerProducer( + request: ProducerOuterClass.Producer + ): ProducerOuterClass.RegisterProducerResponse { val registerProducer = producerService.registerProducer( RegisterProducerRequest( - request.name, request.hostname + request.name, + request.hostname ) ) return ProducerOuterClass.RegisterProducerResponse.newBuilder().setId(registerProducer.toUUID()).build() } -} \ No newline at end of file +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/SubscribeTaskService.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/SubscribeTaskService.kt index 62539d55..a1eadee4 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/SubscribeTaskService.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/SubscribeTaskService.kt @@ -16,11 +16,11 @@ package dev.usbharu.owl.broker.interfaces.grpc -import dev.usbharu.owl.Consumer -import dev.usbharu.owl.SubscribeTaskServiceGrpcKt.SubscribeTaskServiceCoroutineImplBase import dev.usbharu.owl.broker.external.toUUID import dev.usbharu.owl.broker.service.ConsumerService import dev.usbharu.owl.broker.service.RegisterConsumerRequest +import dev.usbharu.owl.generated.Consumer +import dev.usbharu.owl.generated.SubscribeTaskServiceGrpcKt.SubscribeTaskServiceCoroutineImplBase import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext @@ -34,4 +34,4 @@ class SubscribeTaskService( consumerService.registerConsumer(RegisterConsumerRequest(request.name, request.hostname, request.tasksList)) return Consumer.SubscribeTaskResponse.newBuilder().setId(id.toUUID()).build() } -} \ No newline at end of file +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/TaskPublishService.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/TaskPublishService.kt index 10b2154f..22aff532 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/TaskPublishService.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/TaskPublishService.kt @@ -16,15 +16,15 @@ package dev.usbharu.owl.broker.interfaces.grpc -import dev.usbharu.owl.PublishTaskOuterClass -import dev.usbharu.owl.PublishTaskOuterClass.PublishedTask -import dev.usbharu.owl.PublishTaskOuterClass.PublishedTasks -import dev.usbharu.owl.TaskPublishServiceGrpcKt.TaskPublishServiceCoroutineImplBase import dev.usbharu.owl.broker.external.toUUID import dev.usbharu.owl.broker.service.PublishTask import dev.usbharu.owl.broker.service.TaskPublishService import dev.usbharu.owl.common.property.PropertySerializeUtils import dev.usbharu.owl.common.property.PropertySerializerFactory +import dev.usbharu.owl.generated.PublishTaskOuterClass +import dev.usbharu.owl.generated.PublishTaskOuterClass.PublishedTask +import dev.usbharu.owl.generated.PublishTaskOuterClass.PublishedTasks +import dev.usbharu.owl.generated.TaskPublishServiceGrpcKt.TaskPublishServiceCoroutineImplBase import io.grpc.Status import io.grpc.StatusException import org.slf4j.LoggerFactory @@ -39,13 +39,9 @@ class TaskPublishService( TaskPublishServiceCoroutineImplBase(coroutineContext) { override suspend fun publishTask(request: PublishTaskOuterClass.PublishTask): PublishedTask { - logger.warn("aaaaaaaaaaa") - - return try { - val publishedTask = taskPublishService.publishTask( PublishTask( request.name, @@ -61,7 +57,6 @@ class TaskPublishService( } override suspend fun publishTasks(request: PublishTaskOuterClass.PublishTasks): PublishedTasks { - val tasks = request.propertiesArrayList.map { PublishTask( request.name, @@ -79,4 +74,4 @@ class TaskPublishService( private val logger = LoggerFactory.getLogger(dev.usbharu.owl.broker.interfaces.grpc.TaskPublishService::class.java) } -} \ No newline at end of file +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/TaskResultService.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/TaskResultService.kt index 90d10f6b..4c5411e3 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/TaskResultService.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/TaskResultService.kt @@ -17,13 +17,13 @@ package dev.usbharu.owl.broker.interfaces.grpc import com.google.protobuf.Empty -import dev.usbharu.owl.TaskResultOuterClass -import dev.usbharu.owl.TaskResultServiceGrpcKt import dev.usbharu.owl.broker.domain.model.taskresult.TaskResult import dev.usbharu.owl.broker.external.toUUID import dev.usbharu.owl.broker.service.TaskManagementService import dev.usbharu.owl.common.property.PropertySerializeUtils import dev.usbharu.owl.common.property.PropertySerializerFactory +import dev.usbharu.owl.generated.TaskResultOuterClass +import dev.usbharu.owl.generated.TaskResultServiceGrpcKt import io.grpc.Status import io.grpc.StatusException import kotlinx.coroutines.CancellationException @@ -67,4 +67,4 @@ class TaskResultService( companion object { private val logger = LoggerFactory.getLogger(TaskResultService::class.java) } -} \ No newline at end of file +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/TaskResultSubscribeService.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/TaskResultSubscribeService.kt index e0635d9a..6ac87395 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/TaskResultSubscribeService.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/interfaces/grpc/TaskResultSubscribeService.kt @@ -16,11 +16,11 @@ package dev.usbharu.owl.broker.interfaces.grpc -import dev.usbharu.owl.* import dev.usbharu.owl.broker.external.toUUID import dev.usbharu.owl.broker.service.TaskManagementService import dev.usbharu.owl.common.property.PropertySerializeUtils import dev.usbharu.owl.common.property.PropertySerializerFactory +import dev.usbharu.owl.generated.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlin.coroutines.CoroutineContext @@ -41,16 +41,18 @@ class TaskResultSubscribeService( name = it.name attempt = it.attempt success = it.success - results.addAll(it.results.map { - taskResult { - id = it.taskId.toUUID() - success = it.success - attempt = it.attempt - result.putAll(PropertySerializeUtils.serialize(propertySerializerFactory, it.result)) - message = it.message + results.addAll( + it.results.map { + taskResult { + id = it.taskId.toUUID() + success = it.success + attempt = it.attempt + result.putAll(PropertySerializeUtils.serialize(propertySerializerFactory, it.result)) + message = it.message + } } - }) + ) } } } -} \ No newline at end of file +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/AssignQueuedTaskDecider.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/AssignQueuedTaskDecider.kt index 44dfa36a..e58a56b3 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/AssignQueuedTaskDecider.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/AssignQueuedTaskDecider.kt @@ -42,7 +42,5 @@ class AssignQueuedTaskDeciderImpl( ).take(numberOfConcurrent) ) } - } - -} \ No newline at end of file +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/ConsumerService.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/ConsumerService.kt index 84b21095..3b7da438 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/ConsumerService.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/ConsumerService.kt @@ -57,4 +57,4 @@ data class RegisterConsumerRequest( val name: String, val hostname: String, val tasks: List -) \ No newline at end of file +) diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/DefaultPropertySerializerFactory.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/DefaultPropertySerializerFactory.kt index 0eed7b40..84baaf23 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/DefaultPropertySerializerFactory.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/DefaultPropertySerializerFactory.kt @@ -28,4 +28,4 @@ class DefaultPropertySerializerFactory : LongPropertySerializer(), FloatPropertySerializer(), ) - ) \ No newline at end of file + ) diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/ProducerService.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/ProducerService.kt index d781ba45..796d6698 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/ProducerService.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/ProducerService.kt @@ -26,10 +26,8 @@ interface ProducerService { suspend fun registerProducer(producer: RegisterProducerRequest): UUID } - class ProducerServiceImpl(private val producerRepository: ProducerRepository) : ProducerService { override suspend fun registerProducer(producer: RegisterProducerRequest): UUID { - val id = UUID.randomUUID() val saveProducer = Producer( @@ -54,4 +52,4 @@ class ProducerServiceImpl(private val producerRepository: ProducerRepository) : data class RegisterProducerRequest( val name: String, val hostname: String -) \ No newline at end of file +) diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/QueueScanner.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/QueueScanner.kt index 6368c93e..ced1fa20 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/QueueScanner.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/QueueScanner.kt @@ -29,19 +29,14 @@ interface QueueScanner { fun startScan(): Flow } - class QueueScannerImpl(private val queueStore: QueueStore) : QueueScanner { - override fun startScan(): Flow { - return flow { - while (currentCoroutineContext().isActive) { - emitAll(scanQueue()) - delay(1000) - } + override fun startScan(): Flow = flow { + while (currentCoroutineContext().isActive) { + emitAll(scanQueue()) + delay(1000) } } - private fun scanQueue(): Flow { - return queueStore.findByQueuedAtBeforeAndIsActiveIsTrue(Instant.now().minusSeconds(10)) - } - -} \ No newline at end of file + private fun scanQueue(): Flow = + queueStore.findByQueuedAtBeforeAndIsActiveIsTrue(Instant.now().minusSeconds(10)) +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/QueueStore.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/QueueStore.kt index 990e2b76..2dbb6aaa 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/QueueStore.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/QueueStore.kt @@ -32,33 +32,24 @@ interface QueueStore { fun findByQueuedAtBeforeAndIsActiveIsTrue(instant: Instant): Flow } - class QueueStoreImpl(private val queuedTaskRepository: QueuedTaskRepository) : QueueStore { override suspend fun enqueue(queuedTask: QueuedTask) { queuedTaskRepository.save(queuedTask) } - override suspend fun enqueueAll(queuedTaskList: List) { - queuedTaskList.forEach { enqueue(it) } - } + override suspend fun enqueueAll(queuedTaskList: List) = queuedTaskList.forEach { enqueue(it) } override suspend fun dequeue(queuedTask: QueuedTask) { queuedTaskRepository.findByTaskIdAndAssignedConsumerIsNullAndUpdate(queuedTask.task.id, queuedTask) } - override suspend fun dequeueAll(queuedTaskList: List) { - return queuedTaskList.forEach { dequeue(it) } - } + override suspend fun dequeueAll(queuedTaskList: List) = queuedTaskList.forEach { dequeue(it) } override fun findByTaskNameInAndIsActiveIsTrueAndOrderByPriority( tasks: List, limit: Int - ): Flow { - return queuedTaskRepository.findByTaskNameInAndIsActiveIsTrueAndOrderByPriority(tasks, limit) - } + ): Flow = queuedTaskRepository.findByTaskNameInAndIsActiveIsTrueAndOrderByPriority(tasks, limit) - override fun findByQueuedAtBeforeAndIsActiveIsTrue(instant: Instant): Flow { - return queuedTaskRepository.findByQueuedAtBeforeAndIsActiveIsTrue(instant) - } - -} \ No newline at end of file + override fun findByQueuedAtBeforeAndIsActiveIsTrue(instant: Instant): Flow = + queuedTaskRepository.findByQueuedAtBeforeAndIsActiveIsTrue(instant) +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/QueuedTaskAssigner.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/QueuedTaskAssigner.kt index 33c95413..caa061af 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/QueuedTaskAssigner.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/QueuedTaskAssigner.kt @@ -27,7 +27,6 @@ interface QueuedTaskAssigner { fun ready(consumerId: UUID, numberOfConcurrent: Int): Flow } - class QueuedTaskAssignerImpl( private val taskManagementService: TaskManagementService, private val queueStore: QueueStore @@ -49,7 +48,6 @@ class QueuedTaskAssignerImpl( private suspend fun assignTask(queuedTask: QueuedTask, consumerId: UUID): QueuedTask? { return try { - val assignedTaskQueue = queuedTask.copy(assignedConsumer = consumerId, assignedAt = Instant.now(), isActive = false) logger.trace( @@ -78,4 +76,4 @@ class QueuedTaskAssignerImpl( companion object { private val logger = LoggerFactory.getLogger(QueuedTaskAssignerImpl::class.java) } -} \ No newline at end of file +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/RegisterTaskService.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/RegisterTaskService.kt index 58945d42..e9812bce 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/RegisterTaskService.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/RegisterTaskService.kt @@ -24,33 +24,34 @@ import org.slf4j.LoggerFactory interface RegisterTaskService { suspend fun registerTask(taskDefinition: TaskDefinition) - suspend fun unregisterTask(name:String) + suspend fun unregisterTask(name: String) } - class RegisterTaskServiceImpl(private val taskDefinitionRepository: TaskDefinitionRepository) : RegisterTaskService { override suspend fun registerTask(taskDefinition: TaskDefinition) { val definedTask = taskDefinitionRepository.findByName(taskDefinition.name) if (definedTask != null) { logger.debug("Task already defined. name: ${taskDefinition.name}") if (taskDefinition.propertyDefinitionHash != definedTask.propertyDefinitionHash) { - throw IncompatibleTaskException("Task ${taskDefinition.name} has already been defined, and the parameters are incompatible.") + throw IncompatibleTaskException( + "Task ${taskDefinition.name} has already been defined, and the parameters are incompatible." + ) } return } taskDefinitionRepository.save(taskDefinition) - logger.info("Register a new task. name: {}",taskDefinition.name) + logger.info("Register a new task. name: {}", taskDefinition.name) } // todo すでにpublish済みのタスクをどうするか決めさせる override suspend fun unregisterTask(name: String) { taskDefinitionRepository.deleteByName(name) - logger.info("Unregister a task. name: {}",name) + logger.info("Unregister a task. name: {}", name) } - companion object{ + companion object { private val logger = LoggerFactory.getLogger(RegisterTaskServiceImpl::class.java) } -} \ No newline at end of file +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskManagementService.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskManagementService.kt index 29133704..c076fecd 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskManagementService.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskManagementService.kt @@ -31,7 +31,6 @@ import org.slf4j.LoggerFactory import java.time.Instant import java.util.* - interface TaskManagementService { suspend fun startManagement(coroutineScope: CoroutineScope) @@ -75,13 +74,11 @@ class TaskManagementServiceImpl( } } - override fun findAssignableTask(consumerId: UUID, numberOfConcurrent: Int): Flow { return assignQueuedTaskDecider.findAssignableQueue(consumerId, numberOfConcurrent) } private suspend fun enqueueTask(task: Task): QueuedTask { - val definedTask = taskDefinitionRepository.findByName(task.name) ?: throw TaskNotRegisterException("Task ${task.name} not definition.") @@ -113,7 +110,6 @@ class TaskManagementServiceImpl( queueStore.dequeue(timeoutQueue) - val task = taskRepository.findById(timeoutQueue.task.id) ?: throw RecordNotFoundException("Task not found. id: ${timeoutQueue.task.id}") val copy = task.copy(attempt = timeoutQueue.attempt) @@ -148,12 +144,10 @@ class TaskManagementServiceImpl( taskResult.taskId, task.copy(completedAt = completedAt, attempt = taskResult.attempt) ) - } override fun subscribeResult(producerId: UUID): Flow { return flow { - while (currentCoroutineContext().isActive) { taskRepository .findByPublishProducerIdAndCompletedAtIsNotNull(producerId) @@ -163,7 +157,7 @@ class TaskManagementServiceImpl( TaskResults( it.name, it.id, - results.any { it.success }, + results.any { taskResult -> taskResult.success }, it.attempt, results ) @@ -171,12 +165,10 @@ class TaskManagementServiceImpl( } delay(500) } - } - } companion object { private val logger = LoggerFactory.getLogger(TaskManagementServiceImpl::class.java) } -} \ No newline at end of file +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskPublishService.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskPublishService.kt index a6604d10..d1dfbe56 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskPublishService.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskPublishService.kt @@ -78,7 +78,6 @@ class TaskPublishServiceImpl( } override suspend fun publishTasks(list: List): List { - val first = list.first() val definition = taskDefinitionRepository.findByName(first.name) @@ -90,14 +89,14 @@ class TaskPublishServiceImpl( val tasks = list.map { Task( - it.name, - UUID.randomUUID(), - first.producerId, - published, - nextRetry, - null, - 0, - it.properties + name = it.name, + id = UUID.randomUUID(), + publishProducerId = first.producerId, + publishedAt = published, + nextRetry = nextRetry, + completedAt = null, + attempt = 0, + properties = it.properties ) } @@ -111,4 +110,4 @@ class TaskPublishServiceImpl( companion object { private val logger = LoggerFactory.getLogger(TaskPublishServiceImpl::class.java) } -} \ No newline at end of file +} diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskResults.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskResults.kt index a61dbb0c..e4e48d3c 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskResults.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskResults.kt @@ -20,9 +20,9 @@ import dev.usbharu.owl.broker.domain.model.taskresult.TaskResult import java.util.* data class TaskResults( - val name:String, - val id:UUID, - val success:Boolean, - val attempt:Int, + val name: String, + val id: UUID, + val success: Boolean, + val attempt: Int, val results: List ) diff --git a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskScanner.kt b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskScanner.kt index 5d469533..1966a168 100644 --- a/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskScanner.kt +++ b/owl/owl-broker/src/main/kotlin/dev/usbharu/owl/broker/service/TaskScanner.kt @@ -49,4 +49,4 @@ class TaskScannerImpl(private val taskRepository: TaskRepository) : companion object { private val logger = LoggerFactory.getLogger(TaskScannerImpl::class.java) } -} \ No newline at end of file +} diff --git a/owl/owl-broker/src/main/proto/consumer.proto b/owl/owl-broker/src/main/proto/consumer.proto index 252d4a27..8ded7398 100644 --- a/owl/owl-broker/src/main/proto/consumer.proto +++ b/owl/owl-broker/src/main/proto/consumer.proto @@ -1,7 +1,7 @@ syntax = "proto3"; import "uuid.proto"; -option java_package = "dev.usbharu.owl"; +option java_package = "dev.usbharu.owl.generated"; message SubscribeTaskRequest { string name = 1; diff --git a/owl/owl-broker/src/main/proto/definition_task.proto b/owl/owl-broker/src/main/proto/definition_task.proto index 6cab5257..1e7b63f7 100644 --- a/owl/owl-broker/src/main/proto/definition_task.proto +++ b/owl/owl-broker/src/main/proto/definition_task.proto @@ -1,6 +1,6 @@ syntax = "proto3"; -option java_package = "dev.usbharu.owl"; +option java_package = "dev.usbharu.owl.generated"; import "google/protobuf/empty.proto"; import "uuid.proto"; diff --git a/owl/owl-broker/src/main/proto/producer.proto b/owl/owl-broker/src/main/proto/producer.proto index b4bbcd7a..7f3e9f05 100644 --- a/owl/owl-broker/src/main/proto/producer.proto +++ b/owl/owl-broker/src/main/proto/producer.proto @@ -2,7 +2,7 @@ syntax = "proto3"; import "uuid.proto"; -option java_package = "dev.usbharu.owl"; +option java_package = "dev.usbharu.owl.generated"; message Producer { string name = 1; diff --git a/owl/owl-broker/src/main/proto/property.proto b/owl/owl-broker/src/main/proto/property.proto index 138e7e22..bb1f5d48 100644 --- a/owl/owl-broker/src/main/proto/property.proto +++ b/owl/owl-broker/src/main/proto/property.proto @@ -2,7 +2,7 @@ syntax = "proto3"; import "google/protobuf/empty.proto"; -option java_package = "dev.usbharu.owl"; +option java_package = "dev.usbharu.owl.generated"; message Property{ oneof value { diff --git a/owl/owl-broker/src/main/proto/publish_task.proto b/owl/owl-broker/src/main/proto/publish_task.proto index 620e6396..db3292e8 100644 --- a/owl/owl-broker/src/main/proto/publish_task.proto +++ b/owl/owl-broker/src/main/proto/publish_task.proto @@ -4,7 +4,7 @@ import "google/protobuf/timestamp.proto"; import "uuid.proto"; -option java_package = "dev.usbharu.owl"; +option java_package = "dev.usbharu.owl.generated"; message PublishTask { diff --git a/owl/owl-broker/src/main/proto/task.proto b/owl/owl-broker/src/main/proto/task.proto index 48566fdb..7e2621bc 100644 --- a/owl/owl-broker/src/main/proto/task.proto +++ b/owl/owl-broker/src/main/proto/task.proto @@ -3,7 +3,7 @@ import "uuid.proto"; import "google/protobuf/timestamp.proto"; import "property.proto"; -option java_package = "dev.usbharu.owl"; +option java_package = "dev.usbharu.owl.generated"; message ReadyRequest { int32 number_of_concurrent = 1; diff --git a/owl/owl-broker/src/main/proto/task_result.proto b/owl/owl-broker/src/main/proto/task_result.proto index 642f7425..a6a74b9a 100644 --- a/owl/owl-broker/src/main/proto/task_result.proto +++ b/owl/owl-broker/src/main/proto/task_result.proto @@ -3,7 +3,7 @@ import "uuid.proto"; import "google/protobuf/empty.proto"; import "property.proto"; -option java_package = "dev.usbharu.owl"; +option java_package = "dev.usbharu.owl.generated"; message TaskResult { UUID id = 1; diff --git a/owl/owl-broker/src/main/proto/task_result_producer.proto b/owl/owl-broker/src/main/proto/task_result_producer.proto index 6102a020..ac6169eb 100644 --- a/owl/owl-broker/src/main/proto/task_result_producer.proto +++ b/owl/owl-broker/src/main/proto/task_result_producer.proto @@ -2,7 +2,7 @@ syntax = "proto3"; import "uuid.proto"; import "task_result.proto"; -option java_package = "dev.usbharu.owl"; +option java_package = "dev.usbharu.owl.generated"; message TaskResults { string name = 1; diff --git a/owl/owl-broker/src/main/proto/uuid.proto b/owl/owl-broker/src/main/proto/uuid.proto index 26d61001..1dca5916 100644 --- a/owl/owl-broker/src/main/proto/uuid.proto +++ b/owl/owl-broker/src/main/proto/uuid.proto @@ -1,6 +1,6 @@ syntax = "proto3"; -option java_package = "dev.usbharu.owl"; +option java_package = "dev.usbharu.owl.generated"; message UUID { uint64 most_significant_uuid_bits = 1; diff --git a/owl/owl-common/owl-common-serialize-jackson/src/main/kotlin/dev/usbharu/Main.kt b/owl/owl-common/owl-common-serialize-jackson/src/main/kotlin/dev/usbharu/Main.kt index 64eb428e..511cd2b6 100644 --- a/owl/owl-common/owl-common-serialize-jackson/src/main/kotlin/dev/usbharu/Main.kt +++ b/owl/owl-common/owl-common-serialize-jackson/src/main/kotlin/dev/usbharu/Main.kt @@ -18,4 +18,4 @@ package dev.usbharu fun main() { println("Hello World!") -} \ No newline at end of file +} diff --git a/owl/owl-common/owl-common-serialize-jackson/src/main/kotlin/dev/usbharu/owl/common/property/ObjectPropertyValue.kt b/owl/owl-common/owl-common-serialize-jackson/src/main/kotlin/dev/usbharu/owl/common/property/ObjectPropertyValue.kt index 6583d4f1..52fbb844 100644 --- a/owl/owl-common/owl-common-serialize-jackson/src/main/kotlin/dev/usbharu/owl/common/property/ObjectPropertyValue.kt +++ b/owl/owl-common/owl-common-serialize-jackson/src/main/kotlin/dev/usbharu/owl/common/property/ObjectPropertyValue.kt @@ -45,6 +45,5 @@ class ObjectPropertySerializer(private val objectMapper: ObjectMapper) : Propert Class.forName(string.substringAfter("jackson:").substringBefore(":")) ) ) - } -} \ No newline at end of file +} diff --git a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/ReflectionUtils.kt b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/ReflectionUtils.kt index f0a6ff90..e72502a6 100644 --- a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/ReflectionUtils.kt +++ b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/ReflectionUtils.kt @@ -23,4 +23,7 @@ val Class<*>.allFields: List superclass.allFields + declaredFields } else { declaredFields.toList() - }.map { it.trySetAccessible();it } \ No newline at end of file + }.map { + it.trySetAccessible() + it + } diff --git a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/BooleanPropertyValue.kt b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/BooleanPropertyValue.kt index b595f31c..3774f1a2 100644 --- a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/BooleanPropertyValue.kt +++ b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/BooleanPropertyValue.kt @@ -15,19 +15,12 @@ class BooleanPropertyValue(override val value: Boolean) : PropertyValue * */ class BooleanPropertySerializer : PropertySerializer { - override fun isSupported(propertyValue: PropertyValue<*>): Boolean { - return propertyValue.value is Boolean - } + override fun isSupported(propertyValue: PropertyValue<*>): Boolean = propertyValue.value is Boolean - override fun isSupported(string: String): Boolean { - return string.startsWith("bool:") - } + override fun isSupported(string: String): Boolean = string.startsWith("bool:") - override fun serialize(propertyValue: PropertyValue<*>): String { - return "bool:" + propertyValue.value.toString() - } + override fun serialize(propertyValue: PropertyValue<*>): String = "bool:" + propertyValue.value.toString() - override fun deserialize(string: String): PropertyValue { - return BooleanPropertyValue(string.replace("bool:", "").toBoolean()) - } -} \ No newline at end of file + override fun deserialize(string: String): PropertyValue = + BooleanPropertyValue(string.replace("bool:", "").toBoolean()) +} diff --git a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/CustomPropertySerializerFactory.kt b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/CustomPropertySerializerFactory.kt index 00d7a3f3..e7ea1e53 100644 --- a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/CustomPropertySerializerFactory.kt +++ b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/CustomPropertySerializerFactory.kt @@ -23,12 +23,9 @@ package dev.usbharu.owl.common.property */ open class CustomPropertySerializerFactory(private val propertySerializers: Set>) : PropertySerializerFactory { - override fun factory(propertyValue: PropertyValue): PropertySerializer { - return propertySerializers.firstOrNull { it.isSupported(propertyValue) } as PropertySerializer? + override fun factory(propertyValue: PropertyValue): PropertySerializer = + propertySerializers.firstOrNull { it.isSupported(propertyValue) } as PropertySerializer? ?: throw IllegalArgumentException("PropertySerializer not found: $propertyValue") - } - override fun factory(string: String): PropertySerializer<*> { - return propertySerializers.first { it.isSupported(string) } - } -} \ No newline at end of file + override fun factory(string: String): PropertySerializer<*> = propertySerializers.first { it.isSupported(string) } +} diff --git a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/DoublePropertyValue.kt b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/DoublePropertyValue.kt index c201cbaa..035c2114 100644 --- a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/DoublePropertyValue.kt +++ b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/DoublePropertyValue.kt @@ -15,19 +15,12 @@ class DoublePropertyValue(override val value: Double) : PropertyValue() * */ class DoublePropertySerializer : PropertySerializer { - override fun isSupported(propertyValue: PropertyValue<*>): Boolean { - return propertyValue.value is Double - } + override fun isSupported(propertyValue: PropertyValue<*>): Boolean = propertyValue.value is Double - override fun isSupported(string: String): Boolean { - return string.startsWith("double:") - } + override fun isSupported(string: String): Boolean = string.startsWith("double:") - override fun serialize(propertyValue: PropertyValue<*>): String { - return "double:" + propertyValue.value.toString() - } + override fun serialize(propertyValue: PropertyValue<*>): String = "double:" + propertyValue.value.toString() - override fun deserialize(string: String): PropertyValue { - return DoublePropertyValue(string.replace("double:", "").toDouble()) - } -} \ No newline at end of file + override fun deserialize(string: String): PropertyValue = + DoublePropertyValue(string.replace("double:", "").toDouble()) +} diff --git a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/FloatPropertyValue.kt b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/FloatPropertyValue.kt index 0f18c832..dbb6b68d 100644 --- a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/FloatPropertyValue.kt +++ b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/FloatPropertyValue.kt @@ -26,19 +26,12 @@ class FloatPropertyValue(override val value: Float) : PropertyValue() { * */ class FloatPropertySerializer : PropertySerializer { - override fun isSupported(propertyValue: PropertyValue<*>): Boolean { - return propertyValue.value is Float - } + override fun isSupported(propertyValue: PropertyValue<*>): Boolean = propertyValue.value is Float - override fun isSupported(string: String): Boolean { - return string.startsWith("float:") - } + override fun isSupported(string: String): Boolean = string.startsWith("float:") - override fun serialize(propertyValue: PropertyValue<*>): String { - return "float:" + propertyValue.value.toString() - } + override fun serialize(propertyValue: PropertyValue<*>): String = "float:" + propertyValue.value.toString() - override fun deserialize(string: String): PropertyValue { - return FloatPropertyValue(string.replace("float:", "").toFloat()) - } -} \ No newline at end of file + override fun deserialize(string: String): PropertyValue = + FloatPropertyValue(string.replace("float:", "").toFloat()) +} diff --git a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/IntegerPropertyValue.kt b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/IntegerPropertyValue.kt index 9b49c39c..c593fc24 100644 --- a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/IntegerPropertyValue.kt +++ b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/IntegerPropertyValue.kt @@ -31,19 +31,12 @@ class IntegerPropertyValue(override val value: Int) : PropertyValue() { * */ class IntegerPropertySerializer : PropertySerializer { - override fun isSupported(propertyValue: PropertyValue<*>): Boolean { - return propertyValue.value is Int - } + override fun isSupported(propertyValue: PropertyValue<*>): Boolean = propertyValue.value is Int - override fun isSupported(string: String): Boolean { - return string.startsWith("int32:") - } + override fun isSupported(string: String): Boolean = string.startsWith("int32:") - override fun serialize(propertyValue: PropertyValue<*>): String { - return "int32:" + propertyValue.value.toString() - } + override fun serialize(propertyValue: PropertyValue<*>): String = "int32:" + propertyValue.value.toString() - override fun deserialize(string: String): PropertyValue { - return IntegerPropertyValue(string.replace("int32:", "").toInt()) - } -} \ No newline at end of file + override fun deserialize(string: String): PropertyValue = + IntegerPropertyValue(string.replace("int32:", "").toInt()) +} diff --git a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/LongPropertyValue.kt b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/LongPropertyValue.kt index 660b0b69..ebbb80df 100644 --- a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/LongPropertyValue.kt +++ b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/LongPropertyValue.kt @@ -27,19 +27,12 @@ class LongPropertyValue(override val value: Long) : PropertyValue() { * */ class LongPropertySerializer : PropertySerializer { - override fun isSupported(propertyValue: PropertyValue<*>): Boolean { - return propertyValue.value is Long - } + override fun isSupported(propertyValue: PropertyValue<*>): Boolean = propertyValue.value is Long - override fun isSupported(string: String): Boolean { - return string.startsWith("int64:") - } + override fun isSupported(string: String): Boolean = string.startsWith("int64:") - override fun serialize(propertyValue: PropertyValue<*>): String { - return "int64:" + propertyValue.value.toString() - } + override fun serialize(propertyValue: PropertyValue<*>): String = "int64:" + propertyValue.value.toString() - override fun deserialize(string: String): PropertyValue { - return LongPropertyValue(string.replace("int64:", "").toLong()) - } -} \ No newline at end of file + override fun deserialize(string: String): PropertyValue = + LongPropertyValue(string.replace("int64:", "").toLong()) +} diff --git a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/PropertySerializeException.kt b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/PropertySerializeException.kt index 4acc597d..b1d29663 100644 --- a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/PropertySerializeException.kt +++ b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/PropertySerializeException.kt @@ -27,4 +27,4 @@ class PropertySerializeException : RuntimeException { enableSuppression, writableStackTrace ) -} \ No newline at end of file +} diff --git a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/PropertySerializeUtils.kt b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/PropertySerializeUtils.kt index 817f3d24..9ae57a50 100644 --- a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/PropertySerializeUtils.kt +++ b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/PropertySerializeUtils.kt @@ -59,4 +59,4 @@ object PropertySerializeUtils { } }.toMap() } -} \ No newline at end of file +} diff --git a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/PropertySerializer.kt b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/PropertySerializer.kt index 950bd9f4..62e1ceff 100644 --- a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/PropertySerializer.kt +++ b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/PropertySerializer.kt @@ -53,4 +53,4 @@ interface PropertySerializer { * @return デシリアライズされた[PropertyValue] */ fun deserialize(string: String): PropertyValue -} \ No newline at end of file +} diff --git a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/PropertySerializerFactory.kt b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/PropertySerializerFactory.kt index 9983d6cf..22ac4466 100644 --- a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/PropertySerializerFactory.kt +++ b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/PropertySerializerFactory.kt @@ -37,4 +37,4 @@ interface PropertySerializerFactory { * @return 作成されたシリアライザー */ fun factory(string: String): PropertySerializer<*> -} \ No newline at end of file +} diff --git a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/PropertyType.kt b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/PropertyType.kt index 4259c62f..047e7655 100644 --- a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/PropertyType.kt +++ b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/PropertyType.kt @@ -38,4 +38,4 @@ enum class PropertyType { * */ binary -} \ No newline at end of file +} diff --git a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/PropertyValue.kt b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/PropertyValue.kt index d04ca229..cbedabf3 100644 --- a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/PropertyValue.kt +++ b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/PropertyValue.kt @@ -31,9 +31,5 @@ abstract class PropertyValue { * プロパティの型 */ abstract val type: PropertyType - override fun toString(): String { - return "PropertyValue(value=$value, type=$type)" - } - - -} \ No newline at end of file + override fun toString(): String = "PropertyValue(value=$value, type=$type)" +} diff --git a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/StringPropertyValue.kt b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/StringPropertyValue.kt index 5b4cbaa1..edd5c393 100644 --- a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/StringPropertyValue.kt +++ b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/property/StringPropertyValue.kt @@ -15,19 +15,11 @@ class StringPropertyValue(override val value: String) : PropertyValue() * */ class StringPropertyValueSerializer : PropertySerializer { - override fun isSupported(propertyValue: PropertyValue<*>): Boolean { - return propertyValue.value is String - } + override fun isSupported(propertyValue: PropertyValue<*>): Boolean = propertyValue.value is String - override fun isSupported(string: String): Boolean { - return string.startsWith("str:") - } + override fun isSupported(string: String): Boolean = string.startsWith("str:") - override fun serialize(propertyValue: PropertyValue<*>): String { - return "str:" + propertyValue.value - } + override fun serialize(propertyValue: PropertyValue<*>): String = "str:" + propertyValue.value - override fun deserialize(string: String): PropertyValue { - return StringPropertyValue(string.replace("str:", "")) - } -} \ No newline at end of file + override fun deserialize(string: String): PropertyValue = StringPropertyValue(string.replace("str:", "")) +} diff --git a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/retry/ExponentialRetryPolicy.kt b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/retry/ExponentialRetryPolicy.kt index b34cacf5..78918a04 100644 --- a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/retry/ExponentialRetryPolicy.kt +++ b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/retry/ExponentialRetryPolicy.kt @@ -13,5 +13,4 @@ import kotlin.math.roundToLong class ExponentialRetryPolicy(private val firstRetrySeconds: Int = 30) : RetryPolicy { override fun nextRetry(now: Instant, attempt: Int): Instant = now.plusSeconds(firstRetrySeconds.times((2.0).pow(attempt).roundToLong()) - firstRetrySeconds) - -} \ No newline at end of file +} diff --git a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/retry/RetryPolicy.kt b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/retry/RetryPolicy.kt index da6e4ec0..755dd45e 100644 --- a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/retry/RetryPolicy.kt +++ b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/retry/RetryPolicy.kt @@ -33,4 +33,4 @@ interface RetryPolicy { * @return 次のリトライ時刻 */ fun nextRetry(now: Instant, attempt: Int): Instant -} \ No newline at end of file +} diff --git a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/retry/RetryPolicyFactory.kt b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/retry/RetryPolicyFactory.kt index 6948e7bf..75590e84 100644 --- a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/retry/RetryPolicyFactory.kt +++ b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/retry/RetryPolicyFactory.kt @@ -16,7 +16,6 @@ package dev.usbharu.owl.common.retry - import org.slf4j.LoggerFactory interface RetryPolicyFactory { @@ -24,9 +23,7 @@ interface RetryPolicyFactory { } class DefaultRetryPolicyFactory(private val map: Map) : RetryPolicyFactory { - override fun factory(name: String): RetryPolicy { - return map[name] ?: throwException(name) - } + override fun factory(name: String): RetryPolicy = map[name] ?: throwException(name) private fun throwException(name: String): Nothing { logger.warn("RetryPolicy not found. name: {}", name) @@ -36,4 +33,4 @@ class DefaultRetryPolicyFactory(private val map: Map) : Ret companion object { private val logger = LoggerFactory.getLogger(RetryPolicyFactory::class.java) } -} \ No newline at end of file +} diff --git a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/retry/RetryPolicyNotFoundException.kt b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/retry/RetryPolicyNotFoundException.kt index dba37f35..d16ed33f 100644 --- a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/retry/RetryPolicyNotFoundException.kt +++ b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/retry/RetryPolicyNotFoundException.kt @@ -27,4 +27,4 @@ class RetryPolicyNotFoundException : RuntimeException { enableSuppression, writableStackTrace ) -} \ No newline at end of file +} diff --git a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/task/PropertyDefinition.kt b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/task/PropertyDefinition.kt index cbc96b0b..ed1a2b3f 100644 --- a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/task/PropertyDefinition.kt +++ b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/task/PropertyDefinition.kt @@ -36,6 +36,4 @@ class PropertyDefinition(val map: Map) : Map( val task: T, val id: UUID, val published: Instant -) \ No newline at end of file +) diff --git a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/task/Task.kt b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/task/Task.kt index f29d5d2d..e83a2807 100644 --- a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/task/Task.kt +++ b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/task/Task.kt @@ -20,4 +20,4 @@ package dev.usbharu.owl.common.task * タスク * */ -open class Task \ No newline at end of file +open class Task diff --git a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/task/TaskDefinition.kt b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/task/TaskDefinition.kt index 5cced498..57b04aed 100644 --- a/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/task/TaskDefinition.kt +++ b/owl/owl-common/src/main/kotlin/dev/usbharu/owl/common/task/TaskDefinition.kt @@ -107,7 +107,6 @@ interface TaskDefinition { * @return デシリアライズされたタスク */ fun deserialize(value: Map>): T { - val task = try { type.getDeclaredConstructor().newInstance() } catch (e: Exception) { @@ -127,4 +126,4 @@ interface TaskDefinition { return task } -} \ No newline at end of file +} diff --git a/owl/owl-consumer/build.gradle.kts b/owl/owl-consumer/build.gradle.kts index 403f8c88..f629d302 100644 --- a/owl/owl-consumer/build.gradle.kts +++ b/owl/owl-consumer/build.gradle.kts @@ -33,7 +33,7 @@ protobuf { artifact = libs.protoc.gen.grpc.java.get().toString() } create("grpckt") { - artifact = libs.protoc.gen.grpc.kotlin.get().toString() + "jdk8@jar" + artifact = libs.protoc.gen.grpc.kotlin.get().toString() + ":jdk8@jar" } } generateProtoTasks { diff --git a/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/AbstractTaskRunner.kt b/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/AbstractTaskRunner.kt index e1a5125e..18bbff55 100644 --- a/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/AbstractTaskRunner.kt +++ b/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/AbstractTaskRunner.kt @@ -29,5 +29,4 @@ abstract class AbstractTaskRunner>(private val t } abstract suspend fun typedRun(typedParam: T, taskRequest: TaskRequest): TaskResult - -} \ No newline at end of file +} diff --git a/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/Consumer.kt b/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/Consumer.kt index 56ac00c7..0069465d 100644 --- a/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/Consumer.kt +++ b/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/Consumer.kt @@ -16,10 +16,10 @@ package dev.usbharu.owl.consumer -import dev.usbharu.owl.* -import dev.usbharu.owl.Uuid.UUID import dev.usbharu.owl.common.property.PropertySerializeUtils import dev.usbharu.owl.common.property.PropertySerializerFactory +import dev.usbharu.owl.generated.* +import dev.usbharu.owl.generated.Uuid.UUID import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import org.slf4j.LoggerFactory @@ -66,11 +66,13 @@ class Consumer( suspend fun init(name: String, hostname: String) { logger.info("Initialize Consumer name: {} hostname: {}", name, hostname) logger.debug("Registered Tasks: {}", runnerMap.keys) - consumerId = subscribeTaskStub.subscribeTask(subscribeTaskRequest { - this.name = name - this.hostname = hostname - this.tasks.addAll(runnerMap.keys) - }).id + consumerId = subscribeTaskStub.subscribeTask( + subscribeTaskRequest { + this.name = name + this.hostname = hostname + this.tasks.addAll(runnerMap.keys) + } + ).id logger.info("Success initialize consumer. ConsumerID: {}", consumerId) } @@ -84,78 +86,92 @@ class Consumer( while (isActive) { try { taskResultStub - .tasKResult(flow { - assignmentTaskStub - .ready(flow { - requestTask() - }).onEach { - logger.info("Start Task name: {} id: {}", it.name, it.id) - processing.update { it + 1 } + .tasKResult( + flow { + assignmentTaskStub + .ready( + flow { + requestTask() + } + ).onEach { + logger.info("Start Task name: {} id: {}", it.name, it.id) + processing.update { it + 1 } - try { - val taskResult = runnerMap.getValue(it.name).run( - TaskRequest( + try { + val taskResult = runnerMap.getValue(it.name).run( + TaskRequest( + it.name, + java.util.UUID( + it.id.mostSignificantUuidBits, + it.id.leastSignificantUuidBits + ), + it.attempt, + Instant.ofEpochSecond( + it.queuedAt.seconds, + it.queuedAt.nanos.toLong() + ), + PropertySerializeUtils.deserialize( + propertySerializerFactory, + it.propertiesMap + ) + ) + ) + + emit( + taskResult { + this.success = taskResult.success + this.attempt = it.attempt + this.id = it.id + this.result.putAll( + PropertySerializeUtils.serialize( + propertySerializerFactory, + taskResult.result + ) + ) + this.message = taskResult.message + } + ) + logger.info( + "Success execute task. name: {} success: {}", it.name, - java.util.UUID( - it.id.mostSignificantUuidBits, - it.id.leastSignificantUuidBits - ), - it.attempt, - Instant.ofEpochSecond(it.queuedAt.seconds, it.queuedAt.nanos.toLong()), - PropertySerializeUtils.deserialize( - propertySerializerFactory, - it.propertiesMap - ) + taskResult.success ) - ) - - emit(taskResult { - this.success = taskResult.success - this.attempt = it.attempt - this.id = it.id - this.result.putAll( - PropertySerializeUtils.serialize( - propertySerializerFactory, taskResult.result - ) + logger.debug("TRACE RESULT {}", taskResult) + } catch (e: CancellationException) { + logger.warn("Cancelled execute task.", e) + emit( + taskResult { + this.success = false + this.attempt = it.attempt + this.id = it.id + this.message = e.localizedMessage + } ) - this.message = taskResult.message - }) - logger.info( - "Success execute task. name: {} success: {}", - it.name, - taskResult.success - ) - logger.debug("TRACE RESULT {}", taskResult) - } catch (e: CancellationException) { - logger.warn("Cancelled execute task.", e) - emit(taskResult { - this.success = false - this.attempt = it.attempt - this.id = it.id - this.message = e.localizedMessage - }) - throw e - } catch (e: Exception) { - logger.warn("Failed execute task. name: {} id: {}", it.name, it.id, e) - emit(taskResult { - this.success = false - this.attempt = it.attempt - this.id = it.id - this.message = e.localizedMessage - }) - } finally { - logger.debug(" Task name: {} id: {}", it.name, it.id) - processing.update { it - 1 } - concurrent.update { - if (it < 64) { - it + 1 - } else { - 64 + throw e + } catch (e: Exception) { + logger.warn("Failed execute task. name: {} id: {}", it.name, it.id, e) + emit( + taskResult { + this.success = false + this.attempt = it.attempt + this.id = it.id + this.message = e.localizedMessage + } + ) + } finally { + logger.debug(" Task name: {} id: {}", it.name, it.id) + processing.update { it - 1 } + concurrent.update { + if (it < 64) { + it + 1 + } else { + 64 + } } } - } - }.flowOn(Dispatchers.Default).collect() - }) + }.flowOn(Dispatchers.Default).collect() + } + ) } catch (e: CancellationException) { throw e } catch (e: Exception) { @@ -171,14 +187,15 @@ class Consumer( while (coroutineScope.isActive) { val andSet = concurrent.getAndUpdate { 0 } - if (andSet != 0) { logger.debug("Request {} tasks.", andSet) try { - emit(readyRequest { - this.consumerId = this@Consumer.consumerId - this.numberOfConcurrent = andSet - }) + emit( + readyRequest { + this.consumerId = this@Consumer.consumerId + this.numberOfConcurrent = andSet + } + ) } catch (e: CancellationException) { throw e } catch (e: Exception) { @@ -206,4 +223,4 @@ class Consumer( companion object { private val logger = LoggerFactory.getLogger(Consumer::class.java) } -} \ No newline at end of file +} diff --git a/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/Main.kt b/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/Main.kt index 31fba291..9ba808b1 100644 --- a/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/Main.kt +++ b/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/Main.kt @@ -25,5 +25,4 @@ fun main() { standaloneConsumer.init() standaloneConsumer.start() } - -} \ No newline at end of file +} diff --git a/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/ServiceLoaderTaskRunnerLoader.kt b/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/ServiceLoaderTaskRunnerLoader.kt index a34fc37b..1a639b81 100644 --- a/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/ServiceLoaderTaskRunnerLoader.kt +++ b/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/ServiceLoaderTaskRunnerLoader.kt @@ -26,4 +26,4 @@ class ServiceLoaderTaskRunnerLoader : TaskRunnerLoader { override fun load(): Map { return taskRunnerMap } -} \ No newline at end of file +} diff --git a/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/StandaloneConsumer.kt b/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/StandaloneConsumer.kt index 34f75a05..215d9ca1 100644 --- a/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/StandaloneConsumer.kt +++ b/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/StandaloneConsumer.kt @@ -16,11 +16,11 @@ package dev.usbharu.owl.consumer -import dev.usbharu.owl.AssignmentTaskServiceGrpcKt -import dev.usbharu.owl.SubscribeTaskServiceGrpcKt -import dev.usbharu.owl.TaskResultServiceGrpcKt import dev.usbharu.owl.common.property.CustomPropertySerializerFactory import dev.usbharu.owl.common.property.PropertySerializerFactory +import dev.usbharu.owl.generated.AssignmentTaskServiceGrpcKt +import dev.usbharu.owl.generated.SubscribeTaskServiceGrpcKt +import dev.usbharu.owl.generated.TaskResultServiceGrpcKt import io.grpc.ManagedChannelBuilder import java.nio.file.Path @@ -90,9 +90,11 @@ class StandaloneConsumer( */ suspend fun start() { consumer.start() - Runtime.getRuntime().addShutdownHook(Thread { - consumer.stop() - }) + Runtime.getRuntime().addShutdownHook( + Thread { + consumer.stop() + } + ) } /** @@ -102,5 +104,4 @@ class StandaloneConsumer( fun stop() { consumer.stop() } - -} \ No newline at end of file +} diff --git a/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/StandaloneConsumerConfigLoader.kt b/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/StandaloneConsumerConfigLoader.kt index d11bda43..b4c65667 100644 --- a/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/StandaloneConsumerConfigLoader.kt +++ b/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/StandaloneConsumerConfigLoader.kt @@ -43,4 +43,4 @@ object StandaloneConsumerConfigLoader { return StandaloneConsumerConfig(address, port, name, hostname, concurrency) } -} \ No newline at end of file +} diff --git a/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/TaskRequest.kt b/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/TaskRequest.kt index 006856f2..fa735fc9 100644 --- a/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/TaskRequest.kt +++ b/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/TaskRequest.kt @@ -30,9 +30,9 @@ import java.util.* * @property properties タスクに渡されたパラメータ */ data class TaskRequest( - val name:String, - val id:UUID, - val attempt:Int, + val name: String, + val id: UUID, + val attempt: Int, val queuedAt: Instant, - val properties:Map> + val properties: Map> ) diff --git a/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/TaskResult.kt b/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/TaskResult.kt index 07d4bd9f..d6b6bf78 100644 --- a/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/TaskResult.kt +++ b/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/TaskResult.kt @@ -35,4 +35,4 @@ data class TaskResult( return TaskResult(true, result, "") } } -} \ No newline at end of file +} diff --git a/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/TaskRunner.kt b/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/TaskRunner.kt index 613b0166..234a7a68 100644 --- a/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/TaskRunner.kt +++ b/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/TaskRunner.kt @@ -33,4 +33,4 @@ interface TaskRunner { * @return タスク実行結果 */ suspend fun run(taskRequest: TaskRequest): TaskResult -} \ No newline at end of file +} diff --git a/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/TaskRunnerLoader.kt b/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/TaskRunnerLoader.kt index 2581e5de..5b9f211b 100644 --- a/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/TaskRunnerLoader.kt +++ b/owl/owl-consumer/src/main/kotlin/dev/usbharu/owl/consumer/TaskRunnerLoader.kt @@ -18,4 +18,4 @@ package dev.usbharu.owl.consumer interface TaskRunnerLoader { fun load(): Map -} \ No newline at end of file +} diff --git a/owl/owl-producer/owl-producer-api/src/main/kotlin/dev/usbharu/owl/producer/api/OwlProducerBuilder.kt b/owl/owl-producer/owl-producer-api/src/main/kotlin/dev/usbharu/owl/producer/api/OwlProducerBuilder.kt index 0678266e..f0b288cb 100644 --- a/owl/owl-producer/owl-producer-api/src/main/kotlin/dev/usbharu/owl/producer/api/OwlProducerBuilder.kt +++ b/owl/owl-producer/owl-producer-api/src/main/kotlin/dev/usbharu/owl/producer/api/OwlProducerBuilder.kt @@ -43,4 +43,4 @@ interface OwlProducerBuilder

{ * @return 作成された[OwlProducer] */ fun build(): P -} \ No newline at end of file +} diff --git a/owl/owl-producer/owl-producer-api/src/main/kotlin/dev/usbharu/owl/producer/api/OwlProducerBuilderConfig.kt b/owl/owl-producer/owl-producer-api/src/main/kotlin/dev/usbharu/owl/producer/api/OwlProducerBuilderConfig.kt index efedddf0..b0d11048 100644 --- a/owl/owl-producer/owl-producer-api/src/main/kotlin/dev/usbharu/owl/producer/api/OwlProducerBuilderConfig.kt +++ b/owl/owl-producer/owl-producer-api/src/main/kotlin/dev/usbharu/owl/producer/api/OwlProducerBuilderConfig.kt @@ -31,4 +31,4 @@ fun

, C : OwlProducerConfig> OWL( ): P { owlProducerBuilder.apply(owlProducerBuilder.config().apply { configBlock() }) return owlProducerBuilder.build() -} \ No newline at end of file +} diff --git a/owl/owl-producer/owl-producer-api/src/main/kotlin/dev/usbharu/owl/producer/api/OwlProducerConfig.kt b/owl/owl-producer/owl-producer-api/src/main/kotlin/dev/usbharu/owl/producer/api/OwlProducerConfig.kt index 9334b0fd..82618a45 100644 --- a/owl/owl-producer/owl-producer-api/src/main/kotlin/dev/usbharu/owl/producer/api/OwlProducerConfig.kt +++ b/owl/owl-producer/owl-producer-api/src/main/kotlin/dev/usbharu/owl/producer/api/OwlProducerConfig.kt @@ -20,4 +20,4 @@ package dev.usbharu.owl.producer.api * [OwlProducer]の構成 * */ -interface OwlProducerConfig \ No newline at end of file +interface OwlProducerConfig diff --git a/owl/owl-producer/owl-producer-default/build.gradle.kts b/owl/owl-producer/owl-producer-default/build.gradle.kts index 7994b7f9..d4d4fe15 100644 --- a/owl/owl-producer/owl-producer-default/build.gradle.kts +++ b/owl/owl-producer/owl-producer-default/build.gradle.kts @@ -34,7 +34,7 @@ protobuf { artifact = libs.protoc.gen.grpc.java.get().toString() } create("grpckt") { - artifact = libs.protoc.gen.grpc.kotlin.get().toString() + "jdk8@jar" + artifact = libs.protoc.gen.grpc.kotlin.get().toString() + ":jdk8@jar" } } generateProtoTasks { diff --git a/owl/owl-producer/owl-producer-default/src/main/kotlin/dev/usbharu/owl/producer/defaultimpl/DefaultOwlProducer.kt b/owl/owl-producer/owl-producer-default/src/main/kotlin/dev/usbharu/owl/producer/defaultimpl/DefaultOwlProducer.kt index e4a1dfd4..62bbb7b1 100644 --- a/owl/owl-producer/owl-producer-default/src/main/kotlin/dev/usbharu/owl/producer/defaultimpl/DefaultOwlProducer.kt +++ b/owl/owl-producer/owl-producer-default/src/main/kotlin/dev/usbharu/owl/producer/defaultimpl/DefaultOwlProducer.kt @@ -17,12 +17,12 @@ package dev.usbharu.owl.producer.defaultimpl import com.google.protobuf.timestamp -import dev.usbharu.owl.* -import dev.usbharu.owl.Uuid.UUID import dev.usbharu.owl.common.property.PropertySerializeUtils import dev.usbharu.owl.common.task.PublishedTask import dev.usbharu.owl.common.task.Task import dev.usbharu.owl.common.task.TaskDefinition +import dev.usbharu.owl.generated.* +import dev.usbharu.owl.generated.Uuid.UUID import dev.usbharu.owl.producer.api.OwlProducer import java.time.Instant @@ -36,10 +36,12 @@ class DefaultOwlProducer(private val defaultOwlProducerConfig: DefaultOwlProduce override suspend fun start() { producerServiceCoroutineStub = ProducerServiceGrpcKt.ProducerServiceCoroutineStub(defaultOwlProducerConfig.channel) - producerId = producerServiceCoroutineStub.registerProducer(producer { - this.name = defaultOwlProducerConfig.name - this.hostname = defaultOwlProducerConfig.hostname - }).id + producerId = producerServiceCoroutineStub.registerProducer( + producer { + this.name = defaultOwlProducerConfig.name + this.hostname = defaultOwlProducerConfig.hostname + } + ).id defineTaskServiceCoroutineStub = DefinitionTaskServiceGrpcKt.DefinitionTaskServiceCoroutineStub(defaultOwlProducerConfig.channel) @@ -48,17 +50,18 @@ class DefaultOwlProducer(private val defaultOwlProducerConfig: DefaultOwlProduce TaskPublishServiceGrpcKt.TaskPublishServiceCoroutineStub(defaultOwlProducerConfig.channel) } - override suspend fun registerTask(taskDefinition: TaskDefinition) { - defineTaskServiceCoroutineStub.register(taskDefinition { - this.producerId = this@DefaultOwlProducer.producerId - this.name = taskDefinition.name - this.maxRetry = taskDefinition.maxRetry - this.priority = taskDefinition.priority - this.retryPolicy = taskDefinition.retryPolicy - this.timeoutMilli = taskDefinition.timeoutMilli - this.propertyDefinitionHash = taskDefinition.propertyDefinition.hash() - }) + defineTaskServiceCoroutineStub.register( + taskDefinition { + this.producerId = this@DefaultOwlProducer.producerId + this.name = taskDefinition.name + this.maxRetry = taskDefinition.maxRetry + this.priority = taskDefinition.priority + this.retryPolicy = taskDefinition.retryPolicy + this.timeoutMilli = taskDefinition.timeoutMilli + this.propertyDefinitionHash = taskDefinition.propertyDefinition.hash() + } + ) } override suspend fun publishTask(task: T): PublishedTask { @@ -69,7 +72,7 @@ class DefaultOwlProducer(private val defaultOwlProducerConfig: DefaultOwlProduce ) val now = Instant.now() val publishTask = taskPublishServiceCoroutineStub.publishTask( - dev.usbharu.owl.publishTask { + dev.usbharu.owl.generated.publishTask { this.producerId = this@DefaultOwlProducer.producerId this.publishedAt = timestamp { @@ -91,4 +94,4 @@ class DefaultOwlProducer(private val defaultOwlProducerConfig: DefaultOwlProduce override suspend fun stop() { defaultOwlProducerConfig.channel.shutdownNow() } -} \ No newline at end of file +} diff --git a/owl/owl-producer/owl-producer-default/src/main/kotlin/dev/usbharu/owl/producer/defaultimpl/DefaultOwlProducerBuilder.kt b/owl/owl-producer/owl-producer-default/src/main/kotlin/dev/usbharu/owl/producer/defaultimpl/DefaultOwlProducerBuilder.kt index 4e45e9f4..cc7f6787 100644 --- a/owl/owl-producer/owl-producer-default/src/main/kotlin/dev/usbharu/owl/producer/defaultimpl/DefaultOwlProducerBuilder.kt +++ b/owl/owl-producer/owl-producer-default/src/main/kotlin/dev/usbharu/owl/producer/defaultimpl/DefaultOwlProducerBuilder.kt @@ -44,4 +44,4 @@ class DefaultOwlProducerBuilder : OwlProducerBuilder publishTask(task: T): PublishedTask { - val taskDefinition = taskMap.getValue(task::class.java) as TaskDefinition val publishTask = application.get().publishTask( @@ -117,4 +116,4 @@ class EmbeddedOwlProducer( override suspend fun stop() { brokerApplication.stop() } -} \ No newline at end of file +} diff --git a/owl/owl-producer/owl-producer-embedded/src/main/kotlin/dev/usbharu/owl/producer/embedded/EmbeddedOwlProducerBuilder.kt b/owl/owl-producer/owl-producer-embedded/src/main/kotlin/dev/usbharu/owl/producer/embedded/EmbeddedOwlProducerBuilder.kt index e92b6fc9..37b75808 100644 --- a/owl/owl-producer/owl-producer-embedded/src/main/kotlin/dev/usbharu/owl/producer/embedded/EmbeddedOwlProducerBuilder.kt +++ b/owl/owl-producer/owl-producer-embedded/src/main/kotlin/dev/usbharu/owl/producer/embedded/EmbeddedOwlProducerBuilder.kt @@ -46,7 +46,6 @@ class EmbeddedOwlProducerBuilder : OwlProducerBuilder