diff --git a/.github/workflows/pull-request-merge-check.yml b/.github/workflows/pull-request-merge-check.yml index c100e253..eafe81c6 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@v2 + 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.0 + 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/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/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-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-mastodon/src/test/kotlin/util/SpringApplicationTestBase.kt b/hideout-mastodon/src/test/kotlin/util/SpringApplicationTestBase.kt new file mode 100644 index 00000000..158f8c7a --- /dev/null +++ b/hideout-mastodon/src/test/kotlin/util/SpringApplicationTestBase.kt @@ -0,0 +1,22 @@ +/* + * 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 org.springframework.boot.test.context.SpringBootTest + +@SpringBootTest +abstract class SpringApplicationTestBase diff --git a/hideout-mastodon/src/test/kotlin/util/TestTransaction.kt b/hideout-mastodon/src/test/kotlin/util/TestTransaction.kt new file mode 100644 index 00000000..8830ba4e --- /dev/null +++ b/hideout-mastodon/src/test/kotlin/util/TestTransaction.kt @@ -0,0 +1,26 @@ +/* + * 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.application.shared.Transaction + + +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/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