mirror of https://github.com/usbharu/Hideout.git
Merge branch 'develop' into renovate/io.insert-koin-koin-bom-4.x
This commit is contained in:
commit
dfbb3b0f35
|
@ -1,16 +1,14 @@
|
||||||
name: PullRequest Merge Check
|
name: pull-request-merge-check.yml
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
|
||||||
- 'owl/**'
|
|
||||||
branches:
|
branches:
|
||||||
- "develop"
|
- "develop"
|
||||||
types:
|
types:
|
||||||
- opened # default
|
- opened
|
||||||
- reopened # default
|
- reopened
|
||||||
- synchronize # default
|
- synchronize
|
||||||
- ready_for_review # 必要
|
- ready_for_review
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
@ -22,10 +20,43 @@ permissions:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
setup:
|
change:
|
||||||
name: Setup
|
|
||||||
if: github.event.pull_request.draft == false
|
if: github.event.pull_request.draft == false
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
core: ${{ steps.filter.outputs.core }}
|
||||||
|
mastodon: ${{ steps.filter.outputs.mastodon }}
|
||||||
|
activitypub: ${{ steps.filter.outputs.ap }}
|
||||||
|
owl: ${{ steps.filter.outputs.owl }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.PAT }}
|
||||||
|
|
||||||
|
- name: Check Changes
|
||||||
|
uses: dorny/paths-filter@v3
|
||||||
|
id: filter
|
||||||
|
with:
|
||||||
|
filters: |
|
||||||
|
core:
|
||||||
|
- 'hideout-core/**'
|
||||||
|
- 'libs.versions.toml'
|
||||||
|
ap:
|
||||||
|
- 'hideout-activitypub/**'
|
||||||
|
- 'libs.versions.toml'
|
||||||
|
mastodon:
|
||||||
|
- 'hideout-mastodon/**'
|
||||||
|
- 'libs.versions.toml'
|
||||||
|
owl:
|
||||||
|
- 'owl/**'
|
||||||
|
- 'libs.versions.toml'
|
||||||
|
|
||||||
|
hideout-core-setup:
|
||||||
|
needs:
|
||||||
|
- change
|
||||||
|
if: github.event.pull_request.draft == false && needs.change.outputs.core == 'true'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
@ -48,11 +79,101 @@ jobs:
|
||||||
gradle-home-cache-cleanup: true
|
gradle-home-cache-cleanup: true
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: ./gradlew classes --no-daemon
|
run: ./gradlew :hideout-core:classes --no-daemon
|
||||||
|
|
||||||
unit-test:
|
hideout-mastodon-setup:
|
||||||
name: Unit Test
|
needs:
|
||||||
needs: [ setup ]
|
- 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
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
@ -75,39 +196,30 @@ jobs:
|
||||||
- name: Unit Test
|
- name: Unit Test
|
||||||
run: ./hideout-core/gradlew :hideout-core:koverXmlReport
|
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
|
- name: JUnit Test Report
|
||||||
uses: mikepenz/action-junit-report@v4
|
uses: mikepenz/action-junit-report@v4
|
||||||
with:
|
with:
|
||||||
report_paths: '**/TEST-*.xml'
|
report_paths: '**/TEST-*.xml'
|
||||||
|
check_name: 'hideout-core JUnit Test Report'
|
||||||
|
|
||||||
- name: Verify Coverage
|
- name: Upload Coverage Report
|
||||||
run: ./hideout-core/gradlew :hideout-core:koverVerify
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: 'hideout-core.xml'
|
||||||
|
path: 'hideout-core/build/reports/kover/hideout-core.xml'
|
||||||
|
|
||||||
lint:
|
|
||||||
name: Lint
|
hideout-mastodon-unit-test:
|
||||||
needs: [ setup ]
|
needs:
|
||||||
|
- hideout-mastodon-setup
|
||||||
|
- change
|
||||||
|
if: github.event.pull_request.draft == false && needs.change.outputs.mastodon == 'true'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.head_ref }}
|
token: ${{ secrets.PAT }}
|
||||||
token: '${{ secrets.PAT }}'
|
|
||||||
|
|
||||||
- name: Set up JDK 21
|
- name: Set up JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
|
@ -121,8 +233,166 @@ jobs:
|
||||||
cache-read-only: false
|
cache-read-only: false
|
||||||
gradle-home-cache-cleanup: true
|
gradle-home-cache-cleanup: true
|
||||||
|
|
||||||
- name: Build with Gradle
|
- name: Unit Test
|
||||||
run: ./gradlew :hideout-core:detektMain :hideout-mastodon:detektMain
|
run: ./hideout-mastodon/gradlew :hideout-mastodon:koverXmlReport
|
||||||
|
|
||||||
|
- name: JUnit Test Report
|
||||||
|
uses: mikepenz/action-junit-report@v4
|
||||||
|
with:
|
||||||
|
report_paths: '**/TEST-*.xml'
|
||||||
|
check_name: 'hideout-mastodon JUnit Test Report'
|
||||||
|
|
||||||
|
- name: Upload Coverage Report
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: 'hideout-mastodon.xml'
|
||||||
|
path: 'hideout-mastodon/build/reports/kover/hideout-mastodon.xml'
|
||||||
|
|
||||||
|
hideout-activitypub-unit-test:
|
||||||
|
needs:
|
||||||
|
- hideout-activitypub-setup
|
||||||
|
- change
|
||||||
|
if: github.event.pull_request.draft == false && needs.change.outputs.activitypub == 'true'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.PAT }}
|
||||||
|
|
||||||
|
- name: Set up JDK 21
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: '21'
|
||||||
|
distribution: 'temurin'
|
||||||
|
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
with:
|
||||||
|
cache-read-only: false
|
||||||
|
gradle-home-cache-cleanup: true
|
||||||
|
|
||||||
|
- name: Unit Test
|
||||||
|
run: ./hideout-activitypub/gradlew :hideout-activitypub:koverXmlReport
|
||||||
|
|
||||||
|
- name: JUnit Test Report
|
||||||
|
uses: mikepenz/action-junit-report@v4
|
||||||
|
with:
|
||||||
|
report_paths: '**/TEST-*.xml'
|
||||||
|
check_name: 'hideout-activitypub JUnit Test Report'
|
||||||
|
|
||||||
|
- name: Upload Coverage Report
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: 'hideout-activitypub.xml'
|
||||||
|
path: 'hideout-activitypub/build/reports/kover/hideout-activitypub.xml'
|
||||||
|
|
||||||
|
owl-unit-test:
|
||||||
|
needs:
|
||||||
|
- owl-setup
|
||||||
|
- change
|
||||||
|
if: github.event.pull_request.draft == false && needs.change.outputs.owl == 'true'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.PAT }}
|
||||||
|
|
||||||
|
- name: Set up JDK 21
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: '21'
|
||||||
|
distribution: 'temurin'
|
||||||
|
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
with:
|
||||||
|
cache-read-only: false
|
||||||
|
gradle-home-cache-cleanup: true
|
||||||
|
|
||||||
|
- name: Unit Test
|
||||||
|
working-directory: owl
|
||||||
|
run: ./gradlew :koverXmlReport --rerun-tasks
|
||||||
|
|
||||||
|
- name: JUnit Test Report
|
||||||
|
uses: mikepenz/action-junit-report@v4
|
||||||
|
with:
|
||||||
|
report_paths: '**/TEST-*.xml'
|
||||||
|
check_name: 'owl JUnit Test Report'
|
||||||
|
|
||||||
|
- name: Upload Coverage Report
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: 'owl.xml'
|
||||||
|
path: 'owl/build/reports/kover/owl.xml'
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
if: always() && (needs.change.outputs.core == 'true' || needs.change.outputs.activitypub == 'true' || needs.change.outputs.mastodon == 'true' || needs.change.outputs.owl == 'true')
|
||||||
|
needs:
|
||||||
|
- change
|
||||||
|
- hideout-core-unit-test
|
||||||
|
- hideout-mastodon-unit-test
|
||||||
|
- hideout-activitypub-unit-test
|
||||||
|
- owl-unit-test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Download Coverage Report
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: 'hideout-core/build/reports/kover'
|
||||||
|
|
||||||
|
- name: Report Coverage
|
||||||
|
uses: madrapps/jacoco-report@v1.7.1
|
||||||
|
with:
|
||||||
|
paths: |
|
||||||
|
${{ github.workspace }}/hideout-core/build/reports/kover/hideout-core.xml/hideout-core.xml,
|
||||||
|
${{ github.workspace }}/hideout-core/build/reports/kover/hideout-mastodon.xml/hideout-mastodon.xml,
|
||||||
|
${{ github.workspace }}/hideout-core/build/reports/kover/hideout-activitypub.xml/hideout-activitypub.xml
|
||||||
|
${{ github.workspace }}/hideout-core/build/reports/kover/owl.xml/owl.xml
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
title: Code Coverage
|
||||||
|
update-comment: true
|
||||||
|
min-coverage-overall: 50
|
||||||
|
min-coverage-changed-files: 80
|
||||||
|
|
||||||
|
lint:
|
||||||
|
if: always() && (needs.change.outputs.core == 'true' || needs.change.outputs.activitypub == 'true' || needs.change.outputs.mastodon == 'true' || needs.change.outputs.owl == 'true')
|
||||||
|
needs:
|
||||||
|
- change
|
||||||
|
- hideout-core-setup
|
||||||
|
- hideout-mastodon-setup
|
||||||
|
- hideout-activitypub-setup
|
||||||
|
- owl-setup
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.head_ref }}
|
||||||
|
token: ${{ secrets.PAT }}
|
||||||
|
|
||||||
|
- name: Set up JDK 21
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: '21'
|
||||||
|
distribution: 'temurin'
|
||||||
|
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
with:
|
||||||
|
cache-read-only: false
|
||||||
|
gradle-home-cache-cleanup: true
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: ./gradlew :hideout-core:detektMain :hideout-mastodon:detektMain :hideout-activitypub:detektMain
|
||||||
|
|
||||||
|
- name: owl Lint
|
||||||
|
if: always()
|
||||||
|
working-directory: owl
|
||||||
|
run: ./gradlew :detektMain
|
||||||
|
|
||||||
- name: Auto Commit
|
- name: Auto Commit
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
|
|
|
@ -50,3 +50,5 @@ out/
|
||||||
/hideout-mastodon/.kotlin/sessions/
|
/hideout-mastodon/.kotlin/sessions/
|
||||||
/http-client.private.env.json
|
/http-client.private.env.json
|
||||||
/logs/
|
/logs/
|
||||||
|
/hideout-mastodon/logs/
|
||||||
|
/hideout-mastodon/files/
|
||||||
|
|
74
README.md
74
README.md
|
@ -1,75 +1,3 @@
|
||||||
# Hideout
|
# Hideout
|
||||||
|
|
||||||
HideoutはMastodon互換APIを備えたSNSで、ActivityPubに対応し、KotlinとSpring Frameworkを使用して制作されているソフトウェアです。現在は開発中で、SNSとして主要な機能を備えていますが、セキュリティの問題や不安定なテーブル定義などがあるためホストすることはおすすめしません。
|
ActivityPubで繋がれるSNSを目指している
|
||||||
|
|
||||||
## 特徴
|
|
||||||
|
|
||||||
### ActivityPubでつながるネットワーク
|
|
||||||
|
|
||||||
ActivityPubを実装しているためMastodonやMisskey、Pleromaとつながることができます。また、ActivityPub以外の分散型の通信プロトコルを実装することがあるかもしれません。
|
|
||||||
|
|
||||||
### Mastodon互換API
|
|
||||||
|
|
||||||
OAuth2プロバイダーを含めたほとんどのAPIがMastodonとの互換性を持っているため、既存のMastodon クライアントを使用することができます!また、今後Fedibirdやglitch-soc互換のAPIを実装するかもしれません。
|
|
||||||
|
|
||||||
## セルフホスト
|
|
||||||
|
|
||||||
> [!CAUTION]
|
|
||||||
> **免責事項**
|
|
||||||
>
|
|
||||||
> 本ソフトウェアを利用して発生したすべての事象に対して開発者は責任を負いません。
|
|
||||||
> 本ソフトウェアはApache License 2.0を採用しています。
|
|
||||||
|
|
||||||
現時点でセルフホストはおすすめしませんが、実験用としてホストすることはできます!
|
|
||||||
|
|
||||||
### 使用技術
|
|
||||||
|
|
||||||
- **Kotlin** 強力な言語機能でアプリケーションの安全性を高めます。
|
|
||||||
- **Spring Framework** (Spring Boot/Spring Security/Spring Web/Spring Data) 豊富な機能と堅牢なライブラリでソフトウェアの基幹部分を担います。
|
|
||||||
- **OpenAPI** スキーマファーストのエンドポイント自動生成はAPIの安定性を高めます。
|
|
||||||
|
|
||||||
### 要件
|
|
||||||
|
|
||||||
#### 起動
|
|
||||||
|
|
||||||
- Java 21
|
|
||||||
- PostgreSQL 12+
|
|
||||||
- MongoDB(必須でない) 4.4.x+
|
|
||||||
|
|
||||||
#### ビルド
|
|
||||||
|
|
||||||
- Java 21
|
|
||||||
- Gradle 8+
|
|
||||||
|
|
||||||
実験用途として、PostgreSQLはH2DB(バンドル)のPostgreSQL互換モードでも問題ありません。
|
|
||||||
|
|
||||||
Java 17でもビルド/起動ができますが、サポートしません。
|
|
||||||
|
|
||||||
MongoDBを使用しない場合、構成で`hideout.use-mongodb`をfalseにする必要があります。
|
|
||||||
|
|
||||||
### ビルド
|
|
||||||
|
|
||||||
今後のリリースでビルド済みjarなどを使う場合はスキップしてください。
|
|
||||||
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gradle bootJar
|
|
||||||
```
|
|
||||||
|
|
||||||
`build/libs/hideout-x.x.x.jar`が生成されます。
|
|
||||||
|
|
||||||
### 起動
|
|
||||||
|
|
||||||
適切に設定した`application-dev.yml`などをクラスパス上に準備します。
|
|
||||||
|
|
||||||
`dev`は`prod`などに置き換えたり、[複数指定すること](https://spring.pleiades.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties.core.spring.profiles.active)もできます。
|
|
||||||
|
|
||||||
```bash
|
|
||||||
java -jar build/libs/hideout-x.x.x.jar --spring.profiles.active=dev
|
|
||||||
```
|
|
||||||
|
|
||||||
https://spring.pleiades.io/spring-boot/docs/current/reference/html/getting-started.html#getting-started.first-application.executable-jar.gradle
|
|
||||||
|
|
||||||
### 注意事項
|
|
||||||
|
|
||||||
本ソフトウェアは開発中です。正常に機能しない場合があります。また、連合先に迷惑をかける事になる可能性があります。DB/設定ファイル/その他リソースなどの利用方法に破壊的な変更が入る可能性があります。
|
|
|
@ -52,6 +52,7 @@ repositories {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("dev.usbharu:hideout-core:0.0.1")
|
implementation("dev.usbharu:hideout-core:0.0.1")
|
||||||
implementation("dev.usbharu:hideout-mastodon:1.0-SNAPSHOT")
|
implementation("dev.usbharu:hideout-mastodon:1.0-SNAPSHOT")
|
||||||
|
implementation("dev.usbharu:hideout-activitypub:1.0-SNAPSHOT")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register("run") {
|
tasks.register("run") {
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
|
import kotlinx.kover.gradle.plugin.dsl.CoverageUnit
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "1.9.25"
|
alias(libs.plugins.kotlin.jvm)
|
||||||
|
alias(libs.plugins.detekt)
|
||||||
|
alias(libs.plugins.kover)
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "dev.usbharu"
|
group = "dev.usbharu"
|
||||||
|
@ -11,6 +15,7 @@ repositories {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation(kotlin("test"))
|
testImplementation(kotlin("test"))
|
||||||
|
detektPlugins(libs.detekt.formatting)
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.test {
|
tasks.test {
|
||||||
|
@ -19,3 +24,92 @@ tasks.test {
|
||||||
kotlin {
|
kotlin {
|
||||||
jvmToolchain(21)
|
jvmToolchain(21)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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<io.gitlab.arturbosch.detekt.Detekt> {
|
||||||
|
exclude("**/generated/**")
|
||||||
|
setSource("src/main/kotlin")
|
||||||
|
exclude("build/")
|
||||||
|
configureEach {
|
||||||
|
exclude("**/org/koin/ksp/generated/**", "**/generated/**")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
withType<io.gitlab.arturbosch.detekt.DetektCreateBaselineTask>() {
|
||||||
|
configureEach {
|
||||||
|
exclude("**/org/koin/ksp/generated/**", "**/generated/**")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
withType<Test> {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,3 +3,14 @@ plugins {
|
||||||
}
|
}
|
||||||
rootProject.name = "hideout-activitypub"
|
rootProject.name = "hideout-activitypub"
|
||||||
|
|
||||||
|
dependencyResolutionManagement {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
versionCatalogs {
|
||||||
|
create("libs") {
|
||||||
|
from(files("../libs.versions.toml"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -211,12 +211,18 @@ kover {
|
||||||
reports {
|
reports {
|
||||||
verify {
|
verify {
|
||||||
rule {
|
rule {
|
||||||
bound{
|
bound {
|
||||||
minValue = 50
|
minValue = 50
|
||||||
coverageUnits = CoverageUnit.INSTRUCTION
|
coverageUnits = CoverageUnit.INSTRUCTION
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
total {
|
||||||
|
xml {
|
||||||
|
title = "Hideout Core"
|
||||||
|
xmlFile = file("$buildDir/reports/kover/hideout-core.xml")
|
||||||
|
}
|
||||||
|
}
|
||||||
filters {
|
filters {
|
||||||
excludes {
|
excludes {
|
||||||
annotatedBy("org.springframework.context.annotation.Configuration")
|
annotatedBy("org.springframework.context.annotation.Configuration")
|
||||||
|
@ -229,6 +235,7 @@ kover {
|
||||||
packages("org.jetbrains")
|
packages("org.jetbrains")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
|
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
|
||||||
import dev.usbharu.hideout.core.domain.model.media.MediaRepository
|
import dev.usbharu.hideout.core.domain.model.media.MediaRepository
|
||||||
import dev.usbharu.hideout.core.domain.model.support.acct.Acct
|
import dev.usbharu.hideout.core.domain.model.support.acct.Acct
|
||||||
|
import dev.usbharu.hideout.core.domain.model.support.domain.apHost
|
||||||
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
|
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
|
@ -45,7 +46,7 @@ class GetActorDetailApplicationService(
|
||||||
?: throw IllegalArgumentException("Actor ${command.id} not found.")
|
?: throw IllegalArgumentException("Actor ${command.id} not found.")
|
||||||
} else if (command.actorName != null) {
|
} else if (command.actorName != null) {
|
||||||
val host = if (command.actorName.host.isEmpty()) {
|
val host = if (command.actorName.host.isEmpty()) {
|
||||||
applicationConfig.url.host
|
applicationConfig.url.apHost
|
||||||
} else {
|
} else {
|
||||||
command.actorName.host
|
command.actorName.host
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ class RegisterLocalActorApplicationService(
|
||||||
if (actorDomainService.usernameAlreadyUse(command.name)) {
|
if (actorDomainService.usernameAlreadyUse(command.name)) {
|
||||||
throw IllegalArgumentException("Username already exists")
|
throw IllegalArgumentException("Username already exists")
|
||||||
}
|
}
|
||||||
val instance = instanceRepository.findByUrl(applicationConfig.url.toURI())
|
val instance = instanceRepository.findByUrl(applicationConfig.url)
|
||||||
?: throw InternalServerException("Local instance not found.")
|
?: throw InternalServerException("Local instance not found.")
|
||||||
|
|
||||||
val actor = actorFactoryImpl.createLocal(
|
val actor = actorFactoryImpl.createLocal(
|
||||||
|
|
|
@ -16,7 +16,10 @@
|
||||||
|
|
||||||
package dev.usbharu.hideout.core.application.exception
|
package dev.usbharu.hideout.core.application.exception
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
|
||||||
|
|
||||||
class PermissionDeniedException : RuntimeException {
|
class PermissionDeniedException : RuntimeException {
|
||||||
|
constructor(principal: Principal) : super("Permission Denied $principal")
|
||||||
constructor() : super()
|
constructor() : super()
|
||||||
constructor(message: String?) : super(message)
|
constructor(message: String?) : super(message)
|
||||||
constructor(message: String?, cause: Throwable?) : super(message, cause)
|
constructor(message: String?, cause: Throwable?) : super(message, cause)
|
||||||
|
|
|
@ -34,7 +34,8 @@ class UserGetFilterApplicationService(private val filterRepository: FilterReposi
|
||||||
) {
|
) {
|
||||||
override suspend fun internalExecute(command: GetFilter, principal: LocalUser): Filter {
|
override suspend fun internalExecute(command: GetFilter, principal: LocalUser): Filter {
|
||||||
val 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) {
|
if (filter.userDetailId != principal.userDetailId) {
|
||||||
throw PermissionDeniedException()
|
throw PermissionDeniedException()
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ class GetLocalInstanceApplicationService(
|
||||||
}
|
}
|
||||||
|
|
||||||
val instance = (
|
val instance = (
|
||||||
instanceRepository.findByUrl(applicationConfig.url.toURI())
|
instanceRepository.findByUrl(applicationConfig.url)
|
||||||
?: throw InternalServerException("Local instance not found.")
|
?: throw InternalServerException("Local instance not found.")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ package dev.usbharu.hideout.core.application.instance
|
||||||
import dev.usbharu.hideout.core.application.shared.Transaction
|
import dev.usbharu.hideout.core.application.shared.Transaction
|
||||||
import dev.usbharu.hideout.core.config.ApplicationConfig
|
import dev.usbharu.hideout.core.config.ApplicationConfig
|
||||||
import dev.usbharu.hideout.core.domain.model.instance.*
|
import dev.usbharu.hideout.core.domain.model.instance.*
|
||||||
|
import dev.usbharu.hideout.core.domain.model.support.domain.apHost
|
||||||
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
|
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
|
||||||
import org.springframework.boot.context.event.ApplicationReadyEvent
|
import org.springframework.boot.context.event.ApplicationReadyEvent
|
||||||
import org.springframework.boot.info.BuildProperties
|
import org.springframework.boot.info.BuildProperties
|
||||||
|
@ -36,15 +37,15 @@ class InitLocalInstanceApplicationService(
|
||||||
) {
|
) {
|
||||||
@EventListener(ApplicationReadyEvent::class)
|
@EventListener(ApplicationReadyEvent::class)
|
||||||
suspend fun init() = transaction.transaction {
|
suspend fun init() = transaction.transaction {
|
||||||
val findByUrl = instanceRepository.findByUrl(applicationConfig.url.toURI())
|
val findByUrl = instanceRepository.findByUrl(applicationConfig.url)
|
||||||
|
|
||||||
if (findByUrl == null) {
|
if (findByUrl == null) {
|
||||||
val instance = Instance(
|
val instance = Instance(
|
||||||
id = InstanceId(idGenerateService.generateId()),
|
id = InstanceId(idGenerateService.generateId()),
|
||||||
name = InstanceName(applicationConfig.url.host),
|
name = InstanceName(applicationConfig.url.apHost),
|
||||||
description = InstanceDescription(""),
|
description = InstanceDescription(""),
|
||||||
url = applicationConfig.url.toURI(),
|
url = applicationConfig.url,
|
||||||
iconUrl = applicationConfig.url.toURI(),
|
iconUrl = applicationConfig.url,
|
||||||
sharedInbox = null,
|
sharedInbox = null,
|
||||||
software = InstanceSoftware("hideout"),
|
software = InstanceSoftware("hideout"),
|
||||||
version = InstanceVersion(buildProperties.version),
|
version = InstanceVersion(buildProperties.version),
|
||||||
|
|
|
@ -18,6 +18,7 @@ package dev.usbharu.hideout.core.application.model
|
||||||
|
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.Actor
|
import dev.usbharu.hideout.core.domain.model.actor.Actor
|
||||||
import dev.usbharu.hideout.core.domain.model.media.Media
|
import dev.usbharu.hideout.core.domain.model.media.Media
|
||||||
|
import dev.usbharu.hideout.core.domain.model.support.domain.apHost
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
data class ActorDetail(
|
data class ActorDetail(
|
||||||
|
@ -41,7 +42,7 @@ data class ActorDetail(
|
||||||
id = actor.id.id,
|
id = actor.id.id,
|
||||||
name = actor.name.name,
|
name = actor.name.name,
|
||||||
screenName = actor.screenName.screenName,
|
screenName = actor.screenName.screenName,
|
||||||
host = actor.url.host,
|
host = actor.url.apHost,
|
||||||
instanceId = actor.instance.instanceId,
|
instanceId = actor.instance.instanceId,
|
||||||
remoteUrl = actor.url,
|
remoteUrl = actor.url,
|
||||||
locked = actor.locked,
|
locked = actor.locked,
|
||||||
|
|
|
@ -24,6 +24,7 @@ import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
|
||||||
import dev.usbharu.hideout.core.domain.model.relationship.Relationship
|
import dev.usbharu.hideout.core.domain.model.relationship.Relationship
|
||||||
import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository
|
import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository
|
||||||
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
|
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
|
||||||
|
import dev.usbharu.hideout.core.domain.service.relationship.RelationshipDomainService
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
@ -32,6 +33,7 @@ class UserFollowRequestApplicationService(
|
||||||
private val relationshipRepository: RelationshipRepository,
|
private val relationshipRepository: RelationshipRepository,
|
||||||
transaction: Transaction,
|
transaction: Transaction,
|
||||||
private val actorRepository: ActorRepository,
|
private val actorRepository: ActorRepository,
|
||||||
|
private val relationshipDomainService: RelationshipDomainService
|
||||||
) : LocalUserAbstractApplicationService<FollowRequest, Unit>(
|
) : LocalUserAbstractApplicationService<FollowRequest, Unit>(
|
||||||
transaction,
|
transaction,
|
||||||
logger
|
logger
|
||||||
|
@ -43,11 +45,15 @@ class UserFollowRequestApplicationService(
|
||||||
|
|
||||||
val targetId = ActorId(command.targetActorId)
|
val targetId = ActorId(command.targetActorId)
|
||||||
val relationship = relationshipRepository.findByActorIdAndTargetId(actor.id, targetId) ?: Relationship.default(
|
val relationship = relationshipRepository.findByActorIdAndTargetId(actor.id, targetId) ?: Relationship.default(
|
||||||
actor.id,
|
actor.id, targetId
|
||||||
targetId
|
|
||||||
)
|
)
|
||||||
|
|
||||||
relationship.followRequest()
|
val inverseRelationship =
|
||||||
|
relationshipRepository.findByActorIdAndTargetId(targetId, actor.id) ?: Relationship.default(
|
||||||
|
targetId, actor.id
|
||||||
|
)
|
||||||
|
|
||||||
|
relationshipDomainService.followRequest(relationship, inverseRelationship)
|
||||||
|
|
||||||
relationshipRepository.save(relationship)
|
relationshipRepository.save(relationship)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,11 +17,11 @@
|
||||||
package dev.usbharu.hideout.core.config
|
package dev.usbharu.hideout.core.config
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||||
import java.net.URL
|
import java.net.URI
|
||||||
|
|
||||||
@ConfigurationProperties("hideout")
|
@ConfigurationProperties("hideout")
|
||||||
data class ApplicationConfig(
|
data class ApplicationConfig(
|
||||||
val url: URL,
|
val url: URI,
|
||||||
val private: Boolean = true,
|
val private: Boolean = true,
|
||||||
val keySize: Int = 2048,
|
val keySize: Int = 2048,
|
||||||
)
|
)
|
||||||
|
|
|
@ -82,6 +82,16 @@ class Filter(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int = id.hashCode()
|
override fun hashCode(): Int = id.hashCode()
|
||||||
|
override fun toString(): String {
|
||||||
|
return "Filter(" +
|
||||||
|
"id=$id, " +
|
||||||
|
"userDetailId=$userDetailId, " +
|
||||||
|
"name=$name, " +
|
||||||
|
"filterContext=$filterContext, " +
|
||||||
|
"filterAction=$filterAction, " +
|
||||||
|
"filterKeywords=$filterKeywords" +
|
||||||
|
")"
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun isAllow(user: UserDetail, action: Action, resource: Filter): Boolean {
|
fun isAllow(user: UserDetail, action: Action, resource: Filter): Boolean {
|
||||||
|
|
|
@ -30,7 +30,13 @@ class FilterKeyword(
|
||||||
return id == other.id
|
return id == other.id
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int = id.hashCode()
|
||||||
return id.hashCode()
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "FilterKeyword(" +
|
||||||
|
"id=$id, " +
|
||||||
|
"keyword=$keyword, " +
|
||||||
|
"mode=$mode" +
|
||||||
|
")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,8 +29,12 @@ class FilterName(name: String) {
|
||||||
return name == other.name
|
return name == other.name
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int = name.hashCode()
|
||||||
return name.hashCode()
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "FilterName(" +
|
||||||
|
"name='$name'" +
|
||||||
|
")"
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -16,4 +16,29 @@
|
||||||
|
|
||||||
package dev.usbharu.hideout.core.domain.model.filter
|
package dev.usbharu.hideout.core.domain.model.filter
|
||||||
|
|
||||||
class FilterResult(val filter: Filter, val matchedKeyword: String)
|
class FilterResult(val filter: Filter, val matchedKeyword: String) {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as FilterResult
|
||||||
|
|
||||||
|
if (filter != other.filter) return false
|
||||||
|
if (matchedKeyword != other.matchedKeyword) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = filter.hashCode()
|
||||||
|
result = 31 * result + matchedKeyword.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "FilterResult(" +
|
||||||
|
"filter=$filter, " +
|
||||||
|
"matchedKeyword='$matchedKeyword'" +
|
||||||
|
")"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -18,4 +18,29 @@ package dev.usbharu.hideout.core.domain.model.filter
|
||||||
|
|
||||||
import dev.usbharu.hideout.core.domain.model.post.Post
|
import dev.usbharu.hideout.core.domain.model.post.Post
|
||||||
|
|
||||||
class FilteredPost(val post: Post, val filterResults: List<FilterResult>)
|
class FilteredPost(val post: Post, val filterResults: List<FilterResult>) {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as FilteredPost
|
||||||
|
|
||||||
|
if (post != other.post) return false
|
||||||
|
if (filterResults != other.filterResults) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = post.hashCode()
|
||||||
|
result = 31 * result + filterResults.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "FilteredPost(" +
|
||||||
|
"post=$post, " +
|
||||||
|
"filterResults=$filterResults" +
|
||||||
|
")"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -52,8 +52,8 @@ class Relationship(
|
||||||
addDomainEvent(relationshipEventFactory.createEvent(RelationshipEvent.UNFOLLOW_REQUEST))
|
addDomainEvent(relationshipEventFactory.createEvent(RelationshipEvent.UNFOLLOW_REQUEST))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun block() {
|
private fun block() {
|
||||||
require(following.not())
|
unfollow()
|
||||||
blocking = true
|
blocking = true
|
||||||
addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.BLOCK))
|
addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.BLOCK))
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ class Relationship(
|
||||||
mutingFollowRequest = false
|
mutingFollowRequest = false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun followRequest() {
|
private fun followRequest() {
|
||||||
require(blocking.not())
|
require(blocking.not())
|
||||||
followRequesting = true
|
followRequesting = true
|
||||||
addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.FOLLOW_REQUEST))
|
addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.FOLLOW_REQUEST))
|
||||||
|
@ -123,6 +123,17 @@ class Relationship(
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UnnecessaryAbstractClass")
|
||||||
|
abstract class InternalRelationshipDomainService {
|
||||||
|
protected fun block(relationship: Relationship) {
|
||||||
|
relationship.block()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun followRequest(relationship: Relationship) {
|
||||||
|
relationship.followRequest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun default(actorId: ActorId, targetActorId: ActorId): Relationship = Relationship(
|
fun default(actorId: ActorId, targetActorId: ActorId): Relationship = Relationship(
|
||||||
actorId = actorId,
|
actorId = actorId,
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package dev.usbharu.hideout.core.domain.model.support.domain
|
package dev.usbharu.hideout.core.domain.model.support.domain
|
||||||
|
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
@JvmInline
|
@JvmInline
|
||||||
value class Domain(val domain: String) {
|
value class Domain(val domain: String) {
|
||||||
init {
|
init {
|
||||||
|
@ -23,6 +25,15 @@ value class Domain(val domain: String) {
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val LENGTH = 1000
|
const val LENGTH: Int = 1000
|
||||||
|
|
||||||
|
fun of(uri: URI): Domain = Domain(uri.apHost)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val URI.apHost: String
|
||||||
|
get() = if (port == -1) {
|
||||||
|
host
|
||||||
|
} else {
|
||||||
|
"$host:$port"
|
||||||
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package dev.usbharu.hideout.core.domain.service.actor
|
||||||
|
|
||||||
import dev.usbharu.hideout.core.config.ApplicationConfig
|
import dev.usbharu.hideout.core.config.ApplicationConfig
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.Actor
|
import dev.usbharu.hideout.core.domain.model.actor.Actor
|
||||||
|
import dev.usbharu.hideout.core.domain.model.support.domain.apHost
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
interface IRemoteActorCheckDomainService {
|
interface IRemoteActorCheckDomainService {
|
||||||
|
@ -26,5 +27,5 @@ interface IRemoteActorCheckDomainService {
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class RemoteActorCheckDomainService(private val applicationConfig: ApplicationConfig) : IRemoteActorCheckDomainService {
|
class RemoteActorCheckDomainService(private val applicationConfig: ApplicationConfig) : IRemoteActorCheckDomainService {
|
||||||
override fun isRemoteActor(actor: Actor): Boolean = actor.domain.domain != applicationConfig.url.host
|
override fun isRemoteActor(actor: Actor): Boolean = actor.domain.domain != applicationConfig.url.apHost
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import dev.usbharu.hideout.core.config.ApplicationConfig
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.ActorPrivateKey
|
import dev.usbharu.hideout.core.domain.model.actor.ActorPrivateKey
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.ActorPublicKey
|
import dev.usbharu.hideout.core.domain.model.actor.ActorPublicKey
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
|
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
|
||||||
|
import dev.usbharu.hideout.core.domain.model.support.domain.apHost
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import java.security.KeyPairGenerator
|
import java.security.KeyPairGenerator
|
||||||
|
|
||||||
|
@ -29,7 +30,7 @@ class LocalActorDomainServiceImpl(
|
||||||
private val applicationConfig: ApplicationConfig,
|
private val applicationConfig: ApplicationConfig,
|
||||||
) : LocalActorDomainService {
|
) : LocalActorDomainService {
|
||||||
override suspend fun usernameAlreadyUse(name: String): Boolean =
|
override suspend fun usernameAlreadyUse(name: String): Boolean =
|
||||||
actorRepository.findByNameAndDomain(name, applicationConfig.url.host) != null
|
actorRepository.findByNameAndDomain(name, applicationConfig.url.apHost) != null
|
||||||
|
|
||||||
override suspend fun generateKeyPair(): Pair<ActorPublicKey, ActorPrivateKey> {
|
override suspend fun generateKeyPair(): Pair<ActorPublicKey, ActorPrivateKey> {
|
||||||
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
|
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
|
||||||
|
|
|
@ -77,12 +77,12 @@ class DefaultPostReadAccessControl(private val relationshipRepository: Relations
|
||||||
|
|
||||||
override suspend fun areAllows(postList: List<Post>, principal: Principal): List<Post> {
|
override suspend fun areAllows(postList: List<Post>, principal: Principal): List<Post> {
|
||||||
val actorIds = postList.map { it.actorId }
|
val actorIds = postList.map { it.actorId }
|
||||||
val relationshipList =
|
val blockedByList =
|
||||||
relationshipRepository.findByActorIdsAndTargetIdAndBlocking(actorIds, principal.actorId, true)
|
relationshipRepository.findByActorIdsAndTargetIdAndBlocking(actorIds, principal.actorId, true)
|
||||||
.map { it.actorId }
|
.map { it.actorId }
|
||||||
val inverseRelationshipList =
|
val followingList =
|
||||||
relationshipRepository.findByActorIdAndTargetIdsAndFollowing(principal.actorId, actorIds, true)
|
relationshipRepository.findByActorIdAndTargetIdsAndFollowing(principal.actorId, actorIds, true)
|
||||||
.map { it.actorId }
|
.map { it.targetActorId }
|
||||||
|
|
||||||
fun internalAllow(post: Post): Boolean {
|
fun internalAllow(post: Post): Boolean {
|
||||||
// ポスト主は無条件で見れる
|
// ポスト主は無条件で見れる
|
||||||
|
@ -90,7 +90,7 @@ class DefaultPostReadAccessControl(private val relationshipRepository: Relations
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relationshipList.contains(post.actorId)) {
|
if (blockedByList.contains(post.actorId)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ class DefaultPostReadAccessControl(private val relationshipRepository: Relations
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (post.visibility == Visibility.FOLLOWERS && inverseRelationshipList.contains(principal.actorId)) {
|
if (post.visibility == Visibility.FOLLOWERS && followingList.contains(post.actorId)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -17,17 +17,28 @@
|
||||||
package dev.usbharu.hideout.core.domain.service.relationship
|
package dev.usbharu.hideout.core.domain.service.relationship
|
||||||
|
|
||||||
import dev.usbharu.hideout.core.domain.model.relationship.Relationship
|
import dev.usbharu.hideout.core.domain.model.relationship.Relationship
|
||||||
|
import dev.usbharu.hideout.core.domain.model.relationship.Relationship.InternalRelationshipDomainService
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class RelationshipDomainService {
|
class RelationshipDomainService : InternalRelationshipDomainService() {
|
||||||
fun block(relationship: Relationship, inverseRelationship: Relationship) {
|
fun block(relationship: Relationship, inverseRelationship: Relationship) {
|
||||||
require(relationship != inverseRelationship)
|
require(relationship != inverseRelationship)
|
||||||
require(relationship.actorId == inverseRelationship.targetActorId)
|
require(relationship.actorId == inverseRelationship.targetActorId)
|
||||||
require(relationship.targetActorId == inverseRelationship.actorId)
|
require(relationship.targetActorId == inverseRelationship.actorId)
|
||||||
|
|
||||||
relationship.block()
|
block(relationship)
|
||||||
inverseRelationship.unfollow()
|
inverseRelationship.unfollow()
|
||||||
inverseRelationship.unfollowRequest()
|
inverseRelationship.unfollowRequest()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun followRequest(relationship: Relationship, inverseRelationship: Relationship) {
|
||||||
|
require(relationship != inverseRelationship)
|
||||||
|
require(relationship.actorId == inverseRelationship.targetActorId)
|
||||||
|
require(relationship.targetActorId == inverseRelationship.actorId)
|
||||||
|
require(inverseRelationship.blocking.not())
|
||||||
|
require(relationship.blocking.not())
|
||||||
|
|
||||||
|
followRequest(relationship)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
|
import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
|
||||||
import java.sql.SQLException
|
import java.sql.SQLException
|
||||||
|
|
||||||
@Suppress("VarCouldBeVal")
|
@Suppress("VarCouldBeVal", "UnnecessaryAbstractClass")
|
||||||
abstract class AbstractRepository(protected val logger: Logger) {
|
abstract class AbstractRepository(protected val logger: Logger) {
|
||||||
private val sqlErrorCodeSQLExceptionTranslator = SQLErrorCodeSQLExceptionTranslator()
|
private val sqlErrorCodeSQLExceptionTranslator = SQLErrorCodeSQLExceptionTranslator()
|
||||||
private val springDataAccessExceptionSQLExceptionTranslator = SpringDataAccessExceptionSQLExceptionTranslator()
|
private val springDataAccessExceptionSQLExceptionTranslator = SpringDataAccessExceptionSQLExceptionTranslator()
|
||||||
|
|
|
@ -20,6 +20,7 @@ import dev.usbharu.hideout.core.config.ApplicationConfig
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.*
|
import dev.usbharu.hideout.core.domain.model.actor.*
|
||||||
import dev.usbharu.hideout.core.domain.model.instance.InstanceId
|
import dev.usbharu.hideout.core.domain.model.instance.InstanceId
|
||||||
import dev.usbharu.hideout.core.domain.model.support.domain.Domain
|
import dev.usbharu.hideout.core.domain.model.support.domain.Domain
|
||||||
|
import dev.usbharu.hideout.core.domain.model.support.domain.apHost
|
||||||
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
|
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
@ -40,7 +41,7 @@ class ActorFactoryImpl(
|
||||||
return Actor(
|
return Actor(
|
||||||
id = ActorId(idGenerateService.generateId()),
|
id = ActorId(idGenerateService.generateId()),
|
||||||
name = actorName,
|
name = actorName,
|
||||||
domain = Domain(applicationConfig.url.host),
|
domain = Domain(applicationConfig.url.apHost),
|
||||||
screenName = ActorScreenName(name),
|
screenName = ActorScreenName(name),
|
||||||
description = ActorDescription(""),
|
description = ActorDescription(""),
|
||||||
inbox = URI.create("$userUrl/inbox"),
|
inbox = URI.create("$userUrl/inbox"),
|
||||||
|
|
|
@ -19,6 +19,7 @@ package dev.usbharu.hideout.core.infrastructure.springframework.oauth2
|
||||||
import dev.usbharu.hideout.core.application.shared.Transaction
|
import dev.usbharu.hideout.core.application.shared.Transaction
|
||||||
import dev.usbharu.hideout.core.config.ApplicationConfig
|
import dev.usbharu.hideout.core.config.ApplicationConfig
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
|
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
|
||||||
|
import dev.usbharu.hideout.core.domain.model.support.domain.apHost
|
||||||
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository
|
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.slf4j.MDCContext
|
import kotlinx.coroutines.slf4j.MDCContext
|
||||||
|
@ -39,7 +40,7 @@ class UserDetailsServiceImpl(
|
||||||
throw UsernameNotFoundException("Username not found")
|
throw UsernameNotFoundException("Username not found")
|
||||||
}
|
}
|
||||||
transaction.transaction {
|
transaction.transaction {
|
||||||
val actor = actorRepository.findByNameAndDomain(username, applicationConfig.url.host)
|
val actor = actorRepository.findByNameAndDomain(username, applicationConfig.url.apHost)
|
||||||
?: throw UsernameNotFoundException("$username not found")
|
?: throw UsernameNotFoundException("$username not found")
|
||||||
val userDetail = userDetailRepository.findByActorId(actor.id.id)
|
val userDetail = userDetailRepository.findByActorId(actor.id.id)
|
||||||
?: throw UsernameNotFoundException("${actor.id.id} not found")
|
?: throw UsernameNotFoundException("${actor.id.id} not found")
|
||||||
|
|
|
@ -22,7 +22,7 @@ import org.mockito.kotlin.doReturn
|
||||||
import org.mockito.kotlin.eq
|
import org.mockito.kotlin.eq
|
||||||
import org.mockito.kotlin.whenever
|
import org.mockito.kotlin.whenever
|
||||||
import utils.TestTransaction
|
import utils.TestTransaction
|
||||||
import java.net.URL
|
import java.net.URI
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension::class)
|
@ExtendWith(MockitoExtension::class)
|
||||||
class RegisterLocalActorApplicationServiceTest {
|
class RegisterLocalActorApplicationServiceTest {
|
||||||
|
@ -51,7 +51,7 @@ class RegisterLocalActorApplicationServiceTest {
|
||||||
val transaction = TestTransaction
|
val transaction = TestTransaction
|
||||||
|
|
||||||
@Spy
|
@Spy
|
||||||
val applicationConfig = ApplicationConfig(URL("http://example.com"))
|
val applicationConfig = ApplicationConfig(URI.create("http://example.com"))
|
||||||
|
|
||||||
@Spy
|
@Spy
|
||||||
val idGenerateService = TwitterSnowflakeIdGenerateService
|
val idGenerateService = TwitterSnowflakeIdGenerateService
|
||||||
|
|
|
@ -0,0 +1,152 @@
|
||||||
|
package dev.usbharu.hideout.core.domain.model.relationship
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.event.relationship.RelationshipEvent
|
||||||
|
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import utils.AssertDomainEvent.assertContainsEvent
|
||||||
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class RelationshipTest {
|
||||||
|
@Test
|
||||||
|
fun unfollow_フォローとフォローリクエストが取り消されUNFOLLOWとUNFOLLOW_REQUESTが発生する() {
|
||||||
|
val relationship = Relationship(
|
||||||
|
actorId = ActorId(1),
|
||||||
|
targetActorId = ActorId(2),
|
||||||
|
following = true,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequesting = false,
|
||||||
|
mutingFollowRequest = false
|
||||||
|
)
|
||||||
|
|
||||||
|
relationship.unfollow()
|
||||||
|
|
||||||
|
assertFalse(relationship.following)
|
||||||
|
assertFalse(relationship.followRequesting)
|
||||||
|
assertContainsEvent(relationship, RelationshipEvent.UNFOLLOW.eventName)
|
||||||
|
assertContainsEvent(relationship, RelationshipEvent.UNFOLLOW_REQUEST.eventName)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun mute_MUTEが発生する() {
|
||||||
|
val relationship = Relationship(
|
||||||
|
actorId = ActorId(1),
|
||||||
|
targetActorId = ActorId(2),
|
||||||
|
following = true,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequesting = false,
|
||||||
|
mutingFollowRequest = false
|
||||||
|
)
|
||||||
|
|
||||||
|
relationship.mute()
|
||||||
|
|
||||||
|
assertTrue(relationship.muting)
|
||||||
|
assertContainsEvent(relationship, RelationshipEvent.MUTE.eventName)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun unmute_UNMUTEが発生する() {
|
||||||
|
val relationship = Relationship(
|
||||||
|
actorId = ActorId(1),
|
||||||
|
targetActorId = ActorId(2),
|
||||||
|
following = true,
|
||||||
|
blocking = false,
|
||||||
|
muting = true,
|
||||||
|
followRequesting = false,
|
||||||
|
mutingFollowRequest = true
|
||||||
|
)
|
||||||
|
|
||||||
|
relationship.unmute()
|
||||||
|
|
||||||
|
assertFalse(relationship.muting)
|
||||||
|
assertContainsEvent(relationship, RelationshipEvent.UNMUTE.eventName)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun muteFollowRequest_muteFollowiRequestがtrueになる() {
|
||||||
|
val relationship = Relationship(
|
||||||
|
actorId = ActorId(1),
|
||||||
|
targetActorId = ActorId(2),
|
||||||
|
following = true,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequesting = true,
|
||||||
|
mutingFollowRequest = false
|
||||||
|
)
|
||||||
|
|
||||||
|
relationship.muteFollowRequest()
|
||||||
|
|
||||||
|
assertTrue(relationship.mutingFollowRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun unmuteFollowRequest_muteFollowiRequestがfalseになる() {
|
||||||
|
val relationship = Relationship(
|
||||||
|
actorId = ActorId(1),
|
||||||
|
targetActorId = ActorId(2),
|
||||||
|
following = true,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequesting = true,
|
||||||
|
mutingFollowRequest = true
|
||||||
|
)
|
||||||
|
|
||||||
|
relationship.unmuteFollowRequest()
|
||||||
|
|
||||||
|
assertFalse(relationship.mutingFollowRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun unfollowRequest_followRequestingがfalseになりUNFOLLOW_REQUESTが発生する() {
|
||||||
|
val relationship = Relationship(
|
||||||
|
ActorId(1),
|
||||||
|
targetActorId = ActorId(2),
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequesting = true,
|
||||||
|
mutingFollowRequest = false
|
||||||
|
)
|
||||||
|
|
||||||
|
relationship.unfollowRequest()
|
||||||
|
|
||||||
|
assertFalse(relationship.followRequesting)
|
||||||
|
assertContainsEvent(relationship, RelationshipEvent.UNFOLLOW_REQUEST.eventName)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun acceptFollowRequest_followingがtrueにfollowRequestingがfalseになりaccept_followが発生する() {
|
||||||
|
val relationship = Relationship(
|
||||||
|
actorId = ActorId(1),
|
||||||
|
targetActorId = ActorId(2),
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequesting = true,
|
||||||
|
mutingFollowRequest = true
|
||||||
|
)
|
||||||
|
|
||||||
|
relationship.acceptFollowRequest()
|
||||||
|
assertTrue(relationship.following)
|
||||||
|
assertContainsEvent(relationship, RelationshipEvent.ACCEPT_FOLLOW.eventName)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun rejectFollowRequest_followRequestingがfalseになりREJECT_FOLLOWが発生する() {
|
||||||
|
val relationship = Relationship(
|
||||||
|
actorId = ActorId(1),
|
||||||
|
targetActorId = ActorId(2),
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequesting = true,
|
||||||
|
mutingFollowRequest = false
|
||||||
|
)
|
||||||
|
|
||||||
|
relationship.rejectFollowRequest()
|
||||||
|
assertFalse(relationship.followRequesting)
|
||||||
|
assertContainsEvent(relationship, RelationshipEvent.REJECT_FOLLOW.eventName)
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ class RemoteActorCheckDomainServiceTest {
|
||||||
|
|
||||||
val remoteActor = RemoteActorCheckDomainService(
|
val remoteActor = RemoteActorCheckDomainService(
|
||||||
ApplicationConfig(
|
ApplicationConfig(
|
||||||
URI.create("https://local.example.com").toURL()
|
URI.create("https://local.example.com")
|
||||||
)
|
)
|
||||||
).isRemoteActor(
|
).isRemoteActor(
|
||||||
actor
|
actor
|
||||||
|
@ -30,7 +30,7 @@ class RemoteActorCheckDomainServiceTest {
|
||||||
|
|
||||||
val localActor = RemoteActorCheckDomainService(
|
val localActor = RemoteActorCheckDomainService(
|
||||||
ApplicationConfig(
|
ApplicationConfig(
|
||||||
URI.create("https://local.example.com").toURL()
|
URI.create("https://local.example.com")
|
||||||
)
|
)
|
||||||
).isRemoteActor(
|
).isRemoteActor(
|
||||||
actor
|
actor
|
||||||
|
|
|
@ -15,7 +15,7 @@ import org.mockito.junit.jupiter.MockitoExtension
|
||||||
import org.mockito.kotlin.doReturn
|
import org.mockito.kotlin.doReturn
|
||||||
import org.mockito.kotlin.eq
|
import org.mockito.kotlin.eq
|
||||||
import org.mockito.kotlin.whenever
|
import org.mockito.kotlin.whenever
|
||||||
import java.net.URL
|
import java.net.URI
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension::class)
|
@ExtendWith(MockitoExtension::class)
|
||||||
class LocalActorDomainServiceImplTest {
|
class LocalActorDomainServiceImplTest {
|
||||||
|
@ -26,7 +26,7 @@ class LocalActorDomainServiceImplTest {
|
||||||
lateinit var actorRepository: ActorRepository
|
lateinit var actorRepository: ActorRepository
|
||||||
|
|
||||||
@Spy
|
@Spy
|
||||||
val applicationConfig = ApplicationConfig(URL("http://example.com"))
|
val applicationConfig = ApplicationConfig(URI.create("http://example.com"))
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun findByNameAndDomainがnullならfalse() = runTest {
|
fun findByNameAndDomainがnullならfalse() = runTest {
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
package dev.usbharu.hideout.core.domain.service.filter
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.filter.*
|
||||||
|
import dev.usbharu.hideout.core.domain.model.post.TestPostFactory
|
||||||
|
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class FilterDomainServiceTest {
|
||||||
|
@Test
|
||||||
|
fun apply_filterContextの適用範囲にフィルターが適用される() {
|
||||||
|
val post = TestPostFactory.create()
|
||||||
|
|
||||||
|
val domainService = FilterDomainService()
|
||||||
|
val filter = Filter(
|
||||||
|
FilterId(1),
|
||||||
|
userDetailId = UserDetailId(1),
|
||||||
|
FilterName("filter"),
|
||||||
|
setOf(FilterContext.HOME),
|
||||||
|
filterAction = FilterAction.HIDE,
|
||||||
|
setOf(FilterKeyword(FilterKeywordId(1), FilterKeywordKeyword("test"), FilterMode.NONE))
|
||||||
|
)
|
||||||
|
val apply = domainService.apply(post, FilterContext.HOME, listOf(filter))
|
||||||
|
assertEquals(1, apply.filterResults.size)
|
||||||
|
assertEquals("test", apply.filterResults.first().matchedKeyword)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun apply_filterContextに当てはまらないならfilterResultsが空になる() {
|
||||||
|
val post = TestPostFactory.create()
|
||||||
|
|
||||||
|
val domainService = FilterDomainService()
|
||||||
|
val filter = Filter(
|
||||||
|
FilterId(1),
|
||||||
|
userDetailId = UserDetailId(1),
|
||||||
|
FilterName("filter"),
|
||||||
|
setOf(FilterContext.PUBLIC),
|
||||||
|
filterAction = FilterAction.HIDE,
|
||||||
|
setOf(FilterKeyword(FilterKeywordId(1), FilterKeywordKeyword("test"), FilterMode.NONE))
|
||||||
|
)
|
||||||
|
val apply = domainService.apply(post, FilterContext.HOME, listOf(filter))
|
||||||
|
assertEquals(0, apply.filterResults.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun overviewにも適用される() {
|
||||||
|
val post = TestPostFactory.create(overview = "test")
|
||||||
|
|
||||||
|
val domainService = FilterDomainService()
|
||||||
|
val filter = Filter(
|
||||||
|
FilterId(1),
|
||||||
|
userDetailId = UserDetailId(1),
|
||||||
|
FilterName("filter"),
|
||||||
|
setOf(FilterContext.HOME),
|
||||||
|
filterAction = FilterAction.HIDE,
|
||||||
|
setOf(FilterKeyword(FilterKeywordId(1), FilterKeywordKeyword("test"), FilterMode.NONE))
|
||||||
|
)
|
||||||
|
val apply = domainService.apply(post, FilterContext.HOME, listOf(filter))
|
||||||
|
assertEquals(2, apply.filterResults.size)
|
||||||
|
assertEquals("test", apply.filterResults.first().matchedKeyword)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun applyAll_filterContextの適用範囲にフィルターが適用される() {
|
||||||
|
val postList = listOf(
|
||||||
|
TestPostFactory.create(),
|
||||||
|
TestPostFactory.create(),
|
||||||
|
TestPostFactory.create(content = "aaaaaaaaaa"),
|
||||||
|
TestPostFactory.create(),
|
||||||
|
TestPostFactory.create()
|
||||||
|
)
|
||||||
|
val filter = Filter(
|
||||||
|
FilterId(1),
|
||||||
|
userDetailId = UserDetailId(1),
|
||||||
|
FilterName("filter"),
|
||||||
|
setOf(FilterContext.HOME),
|
||||||
|
filterAction = FilterAction.HIDE,
|
||||||
|
setOf(FilterKeyword(FilterKeywordId(1), FilterKeywordKeyword("test"), FilterMode.NONE))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
val filteredPosts = FilterDomainService().applyAll(postList, FilterContext.HOME, filters = listOf(filter))
|
||||||
|
|
||||||
|
assertEquals(5, filteredPosts.size)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package dev.usbharu.hideout.core.domain.service.post
|
package dev.usbharu.hideout.core.domain.service.post
|
||||||
|
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
||||||
|
import dev.usbharu.hideout.core.domain.model.post.Post
|
||||||
import dev.usbharu.hideout.core.domain.model.post.TestPostFactory
|
import dev.usbharu.hideout.core.domain.model.post.TestPostFactory
|
||||||
import dev.usbharu.hideout.core.domain.model.post.Visibility
|
import dev.usbharu.hideout.core.domain.model.post.Visibility
|
||||||
import dev.usbharu.hideout.core.domain.model.relationship.Relationship
|
import dev.usbharu.hideout.core.domain.model.relationship.Relationship
|
||||||
|
@ -13,12 +14,14 @@ import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.jupiter.api.Assertions.assertFalse
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.assertAll
|
||||||
import org.junit.jupiter.api.extension.ExtendWith
|
import org.junit.jupiter.api.extension.ExtendWith
|
||||||
import org.mockito.InjectMocks
|
import org.mockito.InjectMocks
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.junit.jupiter.MockitoExtension
|
import org.mockito.junit.jupiter.MockitoExtension
|
||||||
import org.mockito.kotlin.doReturn
|
import org.mockito.kotlin.*
|
||||||
import org.mockito.kotlin.whenever
|
import kotlin.test.assertContentEquals
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension::class)
|
@ExtendWith(MockitoExtension::class)
|
||||||
class DefaultPostReadAccessControlTest {
|
class DefaultPostReadAccessControlTest {
|
||||||
|
@ -155,4 +158,217 @@ class DefaultPostReadAccessControlTest {
|
||||||
|
|
||||||
assertFalse(actual)
|
assertFalse(actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun ポスト主は無条件で見れる() = runTest {
|
||||||
|
val actual = service.isAllow(
|
||||||
|
TestPostFactory.create(actorId = 1, visibility = Visibility.DIRECT),
|
||||||
|
LocalUser(ActorId(1), UserDetailId(1), Acct("test", "example.com"))
|
||||||
|
)
|
||||||
|
|
||||||
|
assertTrue(actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun areAllows_ポスト主は無条件で見れる() = runTest {
|
||||||
|
whenever(
|
||||||
|
relationshipRepository.findByActorIdsAndTargetIdAndBlocking(
|
||||||
|
any(),
|
||||||
|
anyValueClass(),
|
||||||
|
eq(true)
|
||||||
|
)
|
||||||
|
).doReturn(
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
whenever(
|
||||||
|
relationshipRepository.findByActorIdAndTargetIdsAndFollowing(
|
||||||
|
anyValueClass(),
|
||||||
|
any(),
|
||||||
|
eq(true)
|
||||||
|
)
|
||||||
|
).doReturn(
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
|
val postList = listOf<Post>(
|
||||||
|
TestPostFactory.create(actorId = 1, visibility = Visibility.DIRECT),
|
||||||
|
TestPostFactory.create(actorId = 1, visibility = Visibility.FOLLOWERS),
|
||||||
|
TestPostFactory.create(actorId = 1, visibility = Visibility.UNLISTED),
|
||||||
|
TestPostFactory.create(actorId = 1, visibility = Visibility.PUBLIC),
|
||||||
|
)
|
||||||
|
val actual = service.areAllows(postList, LocalUser(ActorId(1), UserDetailId(1), Acct("test", "example.com")))
|
||||||
|
|
||||||
|
assertContentEquals(postList, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun areFollows_ブロックされていたら見れない() = runTest {
|
||||||
|
whenever(
|
||||||
|
relationshipRepository.findByActorIdsAndTargetIdAndBlocking(
|
||||||
|
any(),
|
||||||
|
anyValueClass(),
|
||||||
|
eq(true)
|
||||||
|
)
|
||||||
|
).doReturn(
|
||||||
|
listOf(Relationship.default(actorId = ActorId(2), targetActorId = ActorId(1)))
|
||||||
|
)
|
||||||
|
whenever(
|
||||||
|
relationshipRepository.findByActorIdAndTargetIdsAndFollowing(
|
||||||
|
anyValueClass(),
|
||||||
|
any(),
|
||||||
|
eq(true)
|
||||||
|
)
|
||||||
|
).doReturn(
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
|
val postList = listOf<Post>(
|
||||||
|
TestPostFactory.create(actorId = 1, visibility = Visibility.DIRECT),
|
||||||
|
TestPostFactory.create(actorId = 2, visibility = Visibility.FOLLOWERS),
|
||||||
|
TestPostFactory.create(actorId = 1, visibility = Visibility.UNLISTED),
|
||||||
|
TestPostFactory.create(actorId = 1, visibility = Visibility.PUBLIC),
|
||||||
|
)
|
||||||
|
val actual = service.areAllows(postList, LocalUser(ActorId(1), UserDetailId(1), Acct("test", "example.com")))
|
||||||
|
|
||||||
|
assertEquals(3, actual.size)
|
||||||
|
assertAll(actual.map { { assertEquals(1, it.actorId.id) } })
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun areAllows_PUBLICとUNLISTEDは見れる() = runTest {
|
||||||
|
whenever(
|
||||||
|
relationshipRepository.findByActorIdsAndTargetIdAndBlocking(
|
||||||
|
any(),
|
||||||
|
anyValueClass(),
|
||||||
|
eq(true)
|
||||||
|
)
|
||||||
|
).doReturn(
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
whenever(
|
||||||
|
relationshipRepository.findByActorIdAndTargetIdsAndFollowing(
|
||||||
|
anyValueClass(),
|
||||||
|
any(),
|
||||||
|
eq(true)
|
||||||
|
)
|
||||||
|
).doReturn(
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
|
val postList = listOf<Post>(
|
||||||
|
TestPostFactory.create(actorId = 3, visibility = Visibility.DIRECT),
|
||||||
|
TestPostFactory.create(actorId = 3, visibility = Visibility.FOLLOWERS),
|
||||||
|
TestPostFactory.create(actorId = 3, visibility = Visibility.UNLISTED),
|
||||||
|
TestPostFactory.create(actorId = 3, visibility = Visibility.PUBLIC),
|
||||||
|
)
|
||||||
|
val actual = service.areAllows(postList, LocalUser(ActorId(1), UserDetailId(1), Acct("test", "example.com")))
|
||||||
|
|
||||||
|
assertEquals(2, actual.size)
|
||||||
|
kotlin.test.assertTrue(actual.all { it.visibility == Visibility.PUBLIC || it.visibility == Visibility.UNLISTED })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun areAllows_Anonymousは見れない() = runTest {
|
||||||
|
whenever(
|
||||||
|
relationshipRepository.findByActorIdsAndTargetIdAndBlocking(
|
||||||
|
any(),
|
||||||
|
anyValueClass(),
|
||||||
|
eq(true)
|
||||||
|
)
|
||||||
|
).doReturn(
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
whenever(
|
||||||
|
relationshipRepository.findByActorIdAndTargetIdsAndFollowing(
|
||||||
|
anyValueClass(),
|
||||||
|
any(),
|
||||||
|
eq(true)
|
||||||
|
)
|
||||||
|
).doReturn(
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
|
val postList = listOf<Post>(
|
||||||
|
TestPostFactory.create(actorId = 3, visibility = Visibility.DIRECT),
|
||||||
|
TestPostFactory.create(actorId = 3, visibility = Visibility.FOLLOWERS),
|
||||||
|
TestPostFactory.create(actorId = 3, visibility = Visibility.UNLISTED),
|
||||||
|
TestPostFactory.create(actorId = 3, visibility = Visibility.PUBLIC),
|
||||||
|
)
|
||||||
|
val actual = service.areAllows(postList, Anonymous)
|
||||||
|
|
||||||
|
assertEquals(2, actual.size)
|
||||||
|
kotlin.test.assertTrue(actual.all { it.visibility == Visibility.PUBLIC || it.visibility == Visibility.UNLISTED })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun areAllows_DIRECTはVisibleActorsに入っていたら見れる() = runTest {
|
||||||
|
whenever(
|
||||||
|
relationshipRepository.findByActorIdsAndTargetIdAndBlocking(
|
||||||
|
any(),
|
||||||
|
anyValueClass(),
|
||||||
|
eq(true)
|
||||||
|
)
|
||||||
|
).doReturn(
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
whenever(
|
||||||
|
relationshipRepository.findByActorIdAndTargetIdsAndFollowing(
|
||||||
|
anyValueClass(),
|
||||||
|
any(),
|
||||||
|
eq(true)
|
||||||
|
)
|
||||||
|
).doReturn(
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
|
val postList = listOf<Post>(
|
||||||
|
TestPostFactory.create(id = 1, actorId = 3, visibility = Visibility.DIRECT, visibleActors = listOf(1)),
|
||||||
|
TestPostFactory.create(id = 2, actorId = 3, visibility = Visibility.DIRECT, visibleActors = listOf(2)),
|
||||||
|
TestPostFactory.create(id = 3, actorId = 3, visibility = Visibility.DIRECT, visibleActors = listOf(3)),
|
||||||
|
TestPostFactory.create(
|
||||||
|
id = 4,
|
||||||
|
actorId = 3,
|
||||||
|
visibility = Visibility.DIRECT,
|
||||||
|
visibleActors = listOf(1, 2, 3, 4)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val actual = service.areAllows(postList, LocalUser(ActorId(1), UserDetailId(1), Acct("test", "example.com")))
|
||||||
|
|
||||||
|
assertEquals(2, actual.size)
|
||||||
|
kotlin.test.assertTrue(actual.all { it.id.id == 1L || it.id.id == 4L })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun areAllows_FOLLOWERSはフォローされていたら見れる() = runTest {
|
||||||
|
whenever(
|
||||||
|
relationshipRepository.findByActorIdsAndTargetIdAndBlocking(
|
||||||
|
any(),
|
||||||
|
anyValueClass(),
|
||||||
|
eq(true)
|
||||||
|
)
|
||||||
|
).doReturn(
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
whenever(
|
||||||
|
relationshipRepository.findByActorIdAndTargetIdsAndFollowing(
|
||||||
|
anyValueClass(),
|
||||||
|
any(),
|
||||||
|
eq(true)
|
||||||
|
)
|
||||||
|
).doReturn(
|
||||||
|
listOf(Relationship.default(actorId = ActorId(1), targetActorId = ActorId(2)))
|
||||||
|
)
|
||||||
|
|
||||||
|
val postList = listOf<Post>(
|
||||||
|
TestPostFactory.create(actorId = 3, visibility = Visibility.FOLLOWERS),
|
||||||
|
TestPostFactory.create(actorId = 2, visibility = Visibility.FOLLOWERS),
|
||||||
|
TestPostFactory.create(actorId = 3, visibility = Visibility.FOLLOWERS),
|
||||||
|
TestPostFactory.create(actorId = 3, visibility = Visibility.FOLLOWERS),
|
||||||
|
)
|
||||||
|
val actual = service.areAllows(postList, LocalUser(ActorId(1), UserDetailId(1), Acct("test", "example.com")))
|
||||||
|
|
||||||
|
assertEquals(1, actual.size)
|
||||||
|
assertAll(actual.map { { assertEquals(2, it.actorId.id) } })
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
package dev.usbharu.hideout.core.domain.service.relationship
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
||||||
|
import dev.usbharu.hideout.core.domain.model.relationship.Relationship
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.assertThrows
|
||||||
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class RelationshipDomainServiceTest {
|
||||||
|
@Test
|
||||||
|
fun block_relationshipとinverseRelationshipが同じ場合失敗() {
|
||||||
|
val relationship = Relationship.default(ActorId(1), ActorId(2))
|
||||||
|
assertThrows<IllegalArgumentException> {
|
||||||
|
RelationshipDomainService().block(relationship, relationship)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun block_relationship_actorIdとinverseRelationshio_targetActorIdが同じ場合失敗() {
|
||||||
|
val relationship = Relationship.default(ActorId(1), ActorId(2))
|
||||||
|
val inverseRelationship = Relationship.default(ActorId(2), ActorId(2))
|
||||||
|
assertThrows<IllegalArgumentException> {
|
||||||
|
RelationshipDomainService().block(relationship, inverseRelationship)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun block_relationship_targetActorIdとinverseRelationship_actorIdが同じ場合失敗() {
|
||||||
|
val relationship = Relationship.default(ActorId(1), ActorId(2))
|
||||||
|
val inverseRelationship = Relationship.default(ActorId(1), ActorId(1))
|
||||||
|
assertThrows<IllegalArgumentException> {
|
||||||
|
RelationshipDomainService().block(relationship, inverseRelationship)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun block_ブロックされお互いのフォローとフォローリクエストが外れる() {
|
||||||
|
val relationship = Relationship(
|
||||||
|
ActorId(1),
|
||||||
|
ActorId(2),
|
||||||
|
following = true,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequesting = false,
|
||||||
|
mutingFollowRequest = false
|
||||||
|
)
|
||||||
|
val inverseRelationship = Relationship(
|
||||||
|
ActorId(2),
|
||||||
|
ActorId(1),
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
followRequesting = true,
|
||||||
|
mutingFollowRequest = false,
|
||||||
|
muting = false
|
||||||
|
)
|
||||||
|
|
||||||
|
RelationshipDomainService().block(relationship, inverseRelationship)
|
||||||
|
|
||||||
|
assertTrue(relationship.blocking)
|
||||||
|
assertFalse(relationship.following)
|
||||||
|
assertFalse(relationship.followRequesting)
|
||||||
|
assertFalse(inverseRelationship.following)
|
||||||
|
assertFalse(inverseRelationship.followRequesting)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun followRequest_relationshipとinverseRelationshipが同じ場合失敗() {
|
||||||
|
val relationship = Relationship.default(ActorId(1), ActorId(2))
|
||||||
|
assertThrows<IllegalArgumentException> {
|
||||||
|
RelationshipDomainService().followRequest(relationship, relationship)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun followRequest_relationship_actorIdとinverseRelationshio_targetActorIdが同じ場合失敗() {
|
||||||
|
val relationship = Relationship.default(ActorId(1), ActorId(2))
|
||||||
|
val inverseRelationship = Relationship.default(ActorId(2), ActorId(2))
|
||||||
|
assertThrows<IllegalArgumentException> {
|
||||||
|
RelationshipDomainService().followRequest(relationship, inverseRelationship)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun followRequest_relationship_targetActorIdとinverseRelationship_actorIdが同じ場合失敗() {
|
||||||
|
val relationship = Relationship.default(ActorId(1), ActorId(2))
|
||||||
|
val inverseRelationship = Relationship.default(ActorId(1), ActorId(1))
|
||||||
|
assertThrows<IllegalArgumentException> {
|
||||||
|
RelationshipDomainService().followRequest(relationship, inverseRelationship)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun followRequest_ブロックされてる場合失敗() {
|
||||||
|
val relationship = Relationship.default(ActorId(1), ActorId(2))
|
||||||
|
val inverseRelationship = Relationship(
|
||||||
|
ActorId(2),
|
||||||
|
ActorId(1),
|
||||||
|
following = false,
|
||||||
|
blocking = true,
|
||||||
|
muting = false,
|
||||||
|
followRequesting = false,
|
||||||
|
mutingFollowRequest = false
|
||||||
|
)
|
||||||
|
assertThrows<IllegalArgumentException> {
|
||||||
|
RelationshipDomainService().followRequest(relationship, inverseRelationship)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun followRequest_ブロックしてる場合は失敗() {
|
||||||
|
val relationship = Relationship(
|
||||||
|
ActorId(1),
|
||||||
|
ActorId(2),
|
||||||
|
following = false,
|
||||||
|
blocking = true,
|
||||||
|
muting = false,
|
||||||
|
followRequesting = false,
|
||||||
|
mutingFollowRequest = false
|
||||||
|
)
|
||||||
|
val inverseRelationship = Relationship.default(ActorId(2), ActorId(1))
|
||||||
|
assertThrows<IllegalArgumentException> {
|
||||||
|
RelationshipDomainService().followRequest(relationship, inverseRelationship)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun followRequest_followRequestingがtrueになる() {
|
||||||
|
val relationship = Relationship.default(ActorId(1), ActorId(2))
|
||||||
|
val inverseRelationship = Relationship.default(ActorId(2), ActorId(1))
|
||||||
|
|
||||||
|
RelationshipDomainService().followRequest(relationship, inverseRelationship)
|
||||||
|
|
||||||
|
assertTrue(relationship.followRequesting)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package dev.usbharu.hideout.core.domain.service.userdetail
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.infrastructure.springframework.SpringSecurityPasswordEncoder
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith
|
||||||
|
import org.mockito.InjectMocks
|
||||||
|
import org.mockito.Spy
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
||||||
|
import kotlin.test.assertNotEquals
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension::class)
|
||||||
|
class UserDetailDomainServiceTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
lateinit var userDetailDomainService: UserDetailDomainService
|
||||||
|
|
||||||
|
@Spy
|
||||||
|
val passwordEncoder: PasswordEncoder = SpringSecurityPasswordEncoder(BCryptPasswordEncoder())
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun hash() = runTest {
|
||||||
|
val hashedPassword = userDetailDomainService.hashPassword("password")
|
||||||
|
|
||||||
|
assertNotEquals("password", hashedPassword.password)
|
||||||
|
}
|
||||||
|
}
|
|
@ -464,7 +464,7 @@ class ExposedRelationshipRepositoryTest : AbstractRepositoryTest(Relationships)
|
||||||
mutingFollowRequest = false,
|
mutingFollowRequest = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
relationship.block()
|
relationship.mute()
|
||||||
|
|
||||||
repository.save(relationship)
|
repository.save(relationship)
|
||||||
|
|
||||||
|
@ -492,7 +492,7 @@ class ExposedRelationshipRepositoryTest : AbstractRepositoryTest(Relationships)
|
||||||
followRequesting = false,
|
followRequesting = false,
|
||||||
mutingFollowRequest = false,
|
mutingFollowRequest = false,
|
||||||
)
|
)
|
||||||
relationship.block()
|
relationship.mute()
|
||||||
|
|
||||||
repository.delete(relationship)
|
repository.delete(relationship)
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ import org.mockito.junit.jupiter.MockitoExtension
|
||||||
import org.mockito.kotlin.*
|
import org.mockito.kotlin.*
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException
|
import org.springframework.security.core.userdetails.UsernameNotFoundException
|
||||||
import utils.TestTransaction
|
import utils.TestTransaction
|
||||||
import java.net.URL
|
import java.net.URI
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension::class)
|
@ExtendWith(MockitoExtension::class)
|
||||||
|
@ -34,7 +34,7 @@ class UserDetailsServiceImplTest {
|
||||||
lateinit var userDetailRepository: UserDetailRepository
|
lateinit var userDetailRepository: UserDetailRepository
|
||||||
|
|
||||||
@Spy
|
@Spy
|
||||||
val applicationConfig = ApplicationConfig(URL("http://example.com"))
|
val applicationConfig = ApplicationConfig(URI.create("http://example.com"))
|
||||||
|
|
||||||
@Spy
|
@Spy
|
||||||
val transaction = TestTransaction
|
val transaction = TestTransaction
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import kotlinx.kover.gradle.plugin.dsl.CoverageUnit
|
||||||
import org.openapitools.generator.gradle.plugin.tasks.GenerateTask
|
import org.openapitools.generator.gradle.plugin.tasks.GenerateTask
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
|
@ -6,6 +7,7 @@ plugins {
|
||||||
alias(libs.plugins.spring.boot)
|
alias(libs.plugins.spring.boot)
|
||||||
alias(libs.plugins.kotlin.spring)
|
alias(libs.plugins.kotlin.spring)
|
||||||
alias(libs.plugins.detekt)
|
alias(libs.plugins.detekt)
|
||||||
|
alias(libs.plugins.kover)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -60,9 +62,14 @@ dependencies {
|
||||||
implementation(libs.bundles.coroutines)
|
implementation(libs.bundles.coroutines)
|
||||||
|
|
||||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
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.kotlin.junit)
|
||||||
testImplementation(libs.coroutines.test)
|
testImplementation(libs.coroutines.test)
|
||||||
testImplementation(libs.h2db)
|
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<io.gitlab.arturbosch.detekt.Detekt> {
|
tasks.withType<io.gitlab.arturbosch.detekt.Detekt> {
|
||||||
exclude("**/generated/**")
|
exclude("**/generated/**")
|
||||||
doFirst {
|
doFirst {
|
||||||
|
|
|
@ -33,7 +33,7 @@ class DeleteFilterV1ApplicationService(private val filterRepository: FilterRepos
|
||||||
) {
|
) {
|
||||||
override suspend fun internalExecute(command: DeleteFilterV1, principal: LocalUser) {
|
override suspend fun internalExecute(command: DeleteFilterV1, principal: LocalUser) {
|
||||||
val filter = filterRepository.findByFilterKeywordId(FilterKeywordId(command.filterKeywordId))
|
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) {
|
if (principal.userDetailId != filter.userDetailId) {
|
||||||
throw PermissionDeniedException()
|
throw PermissionDeniedException()
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ class GetFilterV1ApplicationService(private val filterRepository: FilterReposito
|
||||||
?: throw IllegalArgumentException("Filter ${command.filterKeywordId} not found")
|
?: throw IllegalArgumentException("Filter ${command.filterKeywordId} not found")
|
||||||
|
|
||||||
if (filter.userDetailId != principal.userDetailId) {
|
if (filter.userDetailId != principal.userDetailId) {
|
||||||
throw PermissionDeniedException()
|
throw PermissionDeniedException(principal)
|
||||||
}
|
}
|
||||||
|
|
||||||
val filterKeyword = filter.filterKeywords.find { it.id.id == command.filterKeywordId }
|
val filterKeyword = filter.filterKeywords.find { it.id.id == command.filterKeywordId }
|
||||||
|
|
|
@ -84,6 +84,7 @@ class SpringFilterApi(
|
||||||
account -> FilterContext.ACCOUNT
|
account -> FilterContext.ACCOUNT
|
||||||
}
|
}
|
||||||
}.toSet()
|
}.toSet()
|
||||||
|
val principal = principalContextHolder.getPrincipal()
|
||||||
val filter = userRegisterFilterApplicationService.execute(
|
val filter = userRegisterFilterApplicationService.execute(
|
||||||
RegisterFilter(
|
RegisterFilter(
|
||||||
v1FilterPostRequest.phrase,
|
v1FilterPostRequest.phrase,
|
||||||
|
@ -91,12 +92,12 @@ class SpringFilterApi(
|
||||||
FilterAction.WARN,
|
FilterAction.WARN,
|
||||||
setOf(RegisterFilterKeyword(v1FilterPostRequest.phrase, filterMode))
|
setOf(RegisterFilterKeyword(v1FilterPostRequest.phrase, filterMode))
|
||||||
),
|
),
|
||||||
principalContextHolder.getPrincipal()
|
principal
|
||||||
)
|
)
|
||||||
return ResponseEntity.ok(
|
return ResponseEntity.ok(
|
||||||
getFilterV1ApplicationService.execute(
|
getFilterV1ApplicationService.execute(
|
||||||
GetFilterV1(filter.filterKeywords.first().id),
|
GetFilterV1(filter.filterKeywords.first().id),
|
||||||
principalContextHolder.getPrincipal()
|
principal
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.core.infrastructure.springframework.oauth2.SpringSecurityOauth2PrincipalContextHolder
|
||||||
import dev.usbharu.hideout.mastodon.interfaces.api.generated.MediaApi
|
import dev.usbharu.hideout.mastodon.interfaces.api.generated.MediaApi
|
||||||
import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.MediaAttachment
|
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.http.ResponseEntity
|
||||||
import org.springframework.stereotype.Controller
|
import org.springframework.stereotype.Controller
|
||||||
import org.springframework.web.multipart.MultipartFile
|
import org.springframework.web.multipart.MultipartFile
|
||||||
|
import org.springframework.web.server.ResponseStatusException
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
|
@ -37,6 +40,11 @@ class SpringMediaApi(
|
||||||
description: String?,
|
description: String?,
|
||||||
focus: String?,
|
focus: String?,
|
||||||
): ResponseEntity<MediaAttachment> {
|
): ResponseEntity<MediaAttachment> {
|
||||||
|
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")
|
val tempFile = Files.createTempFile("hideout-tmp-file", ".tmp")
|
||||||
|
|
||||||
Files.newOutputStream(tempFile).use { outputStream ->
|
Files.newOutputStream(tempFile).use { outputStream ->
|
||||||
|
@ -73,4 +81,8 @@ class SpringMediaApi(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = LoggerFactory.getLogger(SpringMediaApi::class.java)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.support.principal.LocalUser
|
||||||
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId
|
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId
|
||||||
import kotlinx.coroutines.test.runTest
|
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.Assertions.assertNull
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
@ -30,4 +32,13 @@ class StatusQueryServiceImplTest {
|
||||||
|
|
||||||
assertNull(status)
|
assertNull(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
@AfterAll
|
||||||
|
fun dropDatabase(@Autowired flyway: Flyway) {
|
||||||
|
flyway.clean()
|
||||||
|
flyway.migrate()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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","<https://example.com/api/v1/accounts/1/statuses?min_id=100>; rel=\"next\", <https://example.com/api/v1/accounts/1/statuses?max_id=81>; rel=\"prev\"") } }
|
||||||
|
.andReturn()
|
||||||
|
.response
|
||||||
|
.contentAsString
|
||||||
|
|
||||||
|
val value = jacksonObjectMapper().readValue(content, object : TypeReference<List<Status>>() {})
|
||||||
|
|
||||||
|
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<List<Status>>() {})
|
||||||
|
|
||||||
|
|
||||||
|
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","<https://example.com/api/v1/accounts/1/statuses?min_id=99>; rel=\"next\", <https://example.com/api/v1/accounts/1/statuses?max_id=80>; rel=\"prev\"") } }
|
||||||
|
.andReturn()
|
||||||
|
.response
|
||||||
|
.contentAsString
|
||||||
|
|
||||||
|
val value = jacksonObjectMapper().readValue(content, object : TypeReference<List<Status>>() {})
|
||||||
|
|
||||||
|
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","<https://example.com/api/v1/accounts/1/statuses?min_id=21>; rel=\"next\", <https://example.com/api/v1/accounts/1/statuses?max_id=2>; rel=\"prev\"") } }
|
||||||
|
.andReturn()
|
||||||
|
.response
|
||||||
|
.contentAsString
|
||||||
|
|
||||||
|
val value = jacksonObjectMapper().readValue(content, object : TypeReference<List<Status>>() {})
|
||||||
|
|
||||||
|
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<DefaultMockMvcBuilder>(SecurityMockMvcConfigurers.springSecurity())
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
@AfterAll
|
||||||
|
fun dropDatabase(@Autowired flyway: Flyway) {
|
||||||
|
flyway.clean()
|
||||||
|
flyway.migrate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<DefaultMockMvcBuilder>(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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<DefaultMockMvcBuilder>(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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<DefaultMockMvcBuilder>(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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<DefaultMockMvcBuilder>(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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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",
|
||||||
|
"<https://example.com/api/v1/notifications?min_id=65>; rel=\"next\", <https://example.com/api/v1/notifications?max_id=26>; rel=\"prev\""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.andReturn()
|
||||||
|
.response
|
||||||
|
.contentAsString
|
||||||
|
|
||||||
|
val value = jacksonObjectMapper().readValue(content, object : TypeReference<List<Notification>>() {})
|
||||||
|
|
||||||
|
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",
|
||||||
|
"<https://example.com/api/v1/notifications?min_id=25>; rel=\"next\", <https://example.com/api/v1/notifications?max_id=1>; rel=\"prev\""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.andReturn()
|
||||||
|
.response
|
||||||
|
.contentAsString
|
||||||
|
|
||||||
|
val value = jacksonObjectMapper().readValue(content, object : TypeReference<List<Notification>>() {})
|
||||||
|
|
||||||
|
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",
|
||||||
|
"<https://example.com/api/v1/notifications?min_id=65>; rel=\"next\", <https://example.com/api/v1/notifications?max_id=26>; rel=\"prev\""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.andReturn()
|
||||||
|
.response
|
||||||
|
.contentAsString
|
||||||
|
|
||||||
|
val value = jacksonObjectMapper().readValue(content, object : TypeReference<List<Notification>>() {})
|
||||||
|
|
||||||
|
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<List<Notification>>() {})
|
||||||
|
|
||||||
|
assertThat(value).size().isZero()
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setUp() {
|
||||||
|
mockMvc = MockMvcBuilders.webAppContextSetup(context)
|
||||||
|
.apply<DefaultMockMvcBuilder>(SecurityMockMvcConfigurers.springSecurity())
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
@AfterAll
|
||||||
|
fun dropDatabase(@Autowired flyway: Flyway) {
|
||||||
|
flyway.clean()
|
||||||
|
flyway.migrate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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",
|
||||||
|
"<https://example.com/api/v1/notifications?min_id=65>; rel=\"next\", <https://example.com/api/v1/notifications?max_id=26>; rel=\"prev\""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.andReturn()
|
||||||
|
.response
|
||||||
|
.contentAsString
|
||||||
|
|
||||||
|
val value = jacksonObjectMapper().readValue(content, object : TypeReference<List<Notification>>() {})
|
||||||
|
|
||||||
|
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",
|
||||||
|
"<https://example.com/api/v1/notifications?min_id=25>; rel=\"next\", <https://example.com/api/v1/notifications?max_id=1>; rel=\"prev\""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.andReturn()
|
||||||
|
.response
|
||||||
|
.contentAsString
|
||||||
|
|
||||||
|
val value = jacksonObjectMapper().readValue(content, object : TypeReference<List<Notification>>() {})
|
||||||
|
|
||||||
|
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",
|
||||||
|
"<https://example.com/api/v1/notifications?min_id=65>; rel=\"next\", <https://example.com/api/v1/notifications?max_id=26>; rel=\"prev\""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.andReturn()
|
||||||
|
.response
|
||||||
|
.contentAsString
|
||||||
|
|
||||||
|
val value = jacksonObjectMapper().readValue(content, object : TypeReference<List<Notification>>() {})
|
||||||
|
|
||||||
|
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<List<Notification>>() {})
|
||||||
|
|
||||||
|
Assertions.assertThat(value).size().isZero()
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setUp() {
|
||||||
|
mockMvc = MockMvcBuilders.webAppContextSetup(context)
|
||||||
|
.apply<DefaultMockMvcBuilder>(SecurityMockMvcConfigurers.springSecurity())
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
@AfterAll
|
||||||
|
fun dropDatabase(@Autowired flyway: Flyway) {
|
||||||
|
flyway.clean()
|
||||||
|
flyway.migrate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<DefaultMockMvcBuilder>(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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<DefaultMockMvcBuilder>(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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
|
|
||||||
|
fun objectMapper(): ObjectMapper {
|
||||||
|
return jacksonObjectMapper()
|
||||||
|
}
|
|
@ -14,9 +14,9 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package dev.usbharu.hideout.core.domain.model.followtimeline
|
package util
|
||||||
|
|
||||||
interface FollowTimelineRepository {
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
suspend fun save(followTimeline: FollowTimeline): FollowTimeline
|
|
||||||
suspend fun delete(followTimeline: FollowTimeline)
|
@SpringBootTest
|
||||||
}
|
abstract class SpringApplicationTestBase
|
|
@ -14,9 +14,13 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package dev.usbharu.hideout.core.domain.model.followtimeline
|
package util
|
||||||
|
|
||||||
import dev.usbharu.hideout.core.domain.model.timeline.TimelineId
|
import dev.usbharu.hideout.core.application.shared.Transaction
|
||||||
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId
|
|
||||||
|
|
||||||
class FollowTimeline(val userDetailId: UserDetailId, val timelineId: TimelineId)
|
|
||||||
|
object TestTransaction : Transaction {
|
||||||
|
override suspend fun <T> transaction(block: suspend () -> T): T = block()
|
||||||
|
|
||||||
|
override suspend fun <T> transaction(transactionLevel: Int, block: suspend () -> T): T = block()
|
||||||
|
}
|
|
@ -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"
|
||||||
|
//)
|
|
@ -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<WithHttpSignature> {
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//}
|
|
@ -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"
|
||||||
|
//)
|
|
@ -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<WithMockHttpSignature> {
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
// }
|
||||||
|
//}
|
|
@ -2,6 +2,8 @@ spring:
|
||||||
datasource:
|
datasource:
|
||||||
url: "jdbc:h2:mem:test;MODE=POSTGRESQL;DB_CLOSE_DELAY=-1;CASE_INSENSITIVE_IDENTIFIERS=true;TRACE_LEVEL_FILE=4;"
|
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
|
driver-class-name: org.h2.Driver
|
||||||
|
flyway:
|
||||||
|
clean-disabled: false
|
||||||
hideout:
|
hideout:
|
||||||
url: "https://test-hideout-dev.usbharu.dev"
|
url: "https://test-hideout-dev.usbharu.dev"
|
||||||
security:
|
security:
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 7.1 KiB |
|
@ -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);
|
|
@ -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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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, '<p>this is test</p>', '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);
|
|
@ -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')
|
|
@ -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, '<p>test post</p>', 'test post', 12345680, 2, 'https://example.com/users/test-user8/posts/1239',
|
||||||
|
null, null, false,
|
||||||
|
'https://example.com/users/test-user8/posts/1239');
|
|
@ -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, '<p>test post</p>', 'test post', 12345680, 0, 'https://example.com/users/test-user4/posts/1237',
|
||||||
|
null, null, false,
|
||||||
|
'https://example.com/users/test-user4/posts/1237');
|
|
@ -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, '<p>test post</p>', 'test post', 12345680, 1, 'https://example.com/users/test-user6/posts/1238',
|
||||||
|
null, null, false,
|
||||||
|
'https://example.com/users/test-user6/posts/1238');
|
|
@ -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, '<p>test post</p>', '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);
|
|
@ -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, '<p>test post</p>', '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, '<p>test post</p>', 'test post', 12345680, 0,
|
||||||
|
'https://example.com/users/test-user10/posts/1241', null, 1240, false,
|
||||||
|
'https://example.com/users/test-user10/posts/1241', false);
|
|
@ -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, '<p>test post</p>', 'test post', 12345680, 2, 'https://example.com/users/test-user3/posts/1236',
|
||||||
|
null, null, false,
|
||||||
|
'https://example.com/users/test-user3/posts/1236', false)
|
|
@ -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, '<p>test post</p>', 'test post', 12345680, 0, 'https://example.com/users/test-user/posts/1234',
|
||||||
|
null, null, false,
|
||||||
|
'https://example.com/users/test-user/posts/1234', false)
|
|
@ -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, '<p>test post</p>', 'test post', 12345680, 1, 'https://example.com/users/test-user2/posts/1235',
|
||||||
|
null, null, false,
|
||||||
|
'https://example.com/users/test-user2/posts/1235', false)
|
|
@ -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);
|
|
@ -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);
|
|
@ -1,15 +1,15 @@
|
||||||
insert into posts (id, actor_id, instance_id, overview, content, text, created_at, visibility, url, repost_id, reply_id,
|
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)
|
sensitive, ap_id, deleted, hide, move_to)
|
||||||
values (1, 1, 1, null, 'content', 'text', current_timestamp, 'PUBLIC', 'https://example.com', null, null, false,
|
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,
|
(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,
|
(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,
|
(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,
|
(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)
|
insert into posts_visible_actors(post_id, actor_id)
|
||||||
VALUES (5, 2);
|
VALUES (5, 2);
|
|
@ -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');
|
|
@ -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,'<p>test post</p>', 'hello', 1234455, 0, 'https://localhost/users/1/posts/1', null, null, false,
|
||||||
|
'https://users/1/posts/1');
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
|
@ -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);
|
|
@ -124,7 +124,7 @@ grpc-kotlin = ["grpc-kotlin-stub", "grpc-netty", "grpc-protobuf", "protobuf-kotl
|
||||||
[plugins]
|
[plugins]
|
||||||
|
|
||||||
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||||
spring-boot = { id = "org.springframework.boot", version = "3.3.3" }
|
spring-boot = { id = "org.springframework.boot", version = "3.3.4" }
|
||||||
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
|
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
|
||||||
kotlin-spring = { id = "org.jetbrains.kotlin.plugin.spring", version.ref = "kotlin" }
|
kotlin-spring = { id = "org.jetbrains.kotlin.plugin.spring", version.ref = "kotlin" }
|
||||||
kover = { id = "org.jetbrains.kotlinx.kover", version = "0.8.3" }
|
kover = { id = "org.jetbrains.kotlinx.kover", version = "0.8.3" }
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
|
import kotlinx.kover.gradle.plugin.dsl.CoverageUnit
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.kotlin.jvm)
|
alias(libs.plugins.kotlin.jvm)
|
||||||
id("maven-publish")
|
id("maven-publish")
|
||||||
|
alias(libs.plugins.kover)
|
||||||
|
alias(libs.plugins.detekt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,6 +31,8 @@ subprojects {
|
||||||
apply {
|
apply {
|
||||||
plugin("org.jetbrains.kotlin.jvm")
|
plugin("org.jetbrains.kotlin.jvm")
|
||||||
plugin("maven-publish")
|
plugin("maven-publish")
|
||||||
|
plugin(rootProject.libs.plugins.kover.get().pluginId)
|
||||||
|
plugin(rootProject.libs.plugins.detekt.get().pluginId)
|
||||||
}
|
}
|
||||||
kotlin {
|
kotlin {
|
||||||
jvmToolchain(21)
|
jvmToolchain(21)
|
||||||
|
@ -35,13 +41,43 @@ subprojects {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("org.slf4j:slf4j-api:2.0.15")
|
implementation("org.slf4j:slf4j-api:2.0.15")
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter:5.10.3")
|
testImplementation("org.junit.jupiter:junit-jupiter:5.10.3")
|
||||||
|
detektPlugins(rootProject.libs.detekt.formatting)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.test {
|
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<io.gitlab.arturbosch.detekt.Detekt> {
|
||||||
|
exclude("**/generated/**")
|
||||||
|
setSource("src/main/kotlin")
|
||||||
|
exclude("build/")
|
||||||
|
configureEach {
|
||||||
|
exclude("**/org/koin/ksp/generated/**", "**/generated/**")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
withType<io.gitlab.arturbosch.detekt.DetektCreateBaselineTask>() {
|
||||||
|
configureEach {
|
||||||
|
exclude("**/org/koin/ksp/generated/**", "**/generated/**")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
withType<Test> {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
repositories {
|
repositories {
|
||||||
|
@ -70,3 +106,62 @@ subprojects {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
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
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|
|
@ -35,7 +35,7 @@ protobuf {
|
||||||
artifact = libs.protoc.gen.grpc.java.get().toString()
|
artifact = libs.protoc.gen.grpc.java.get().toString()
|
||||||
}
|
}
|
||||||
create("grpckt") {
|
create("grpckt") {
|
||||||
artifact = libs.protoc.gen.grpc.kotlin.get().toString() + "jdk8@jar"
|
artifact = libs.protoc.gen.grpc.kotlin.get().toString() + ":jdk8@jar"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
generateProtoTasks {
|
generateProtoTasks {
|
||||||
|
|
|
@ -32,7 +32,6 @@ import org.koin.dsl.module
|
||||||
|
|
||||||
class MongoModuleContext : ModuleContext {
|
class MongoModuleContext : ModuleContext {
|
||||||
override fun module(): Module {
|
override fun module(): Module {
|
||||||
|
|
||||||
return module {
|
return module {
|
||||||
single {
|
single {
|
||||||
val clientSettings =
|
val clientSettings =
|
||||||
|
@ -47,7 +46,6 @@ class MongoModuleContext : ModuleContext {
|
||||||
)
|
)
|
||||||
.uuidRepresentation(UuidRepresentation.STANDARD).build()
|
.uuidRepresentation(UuidRepresentation.STANDARD).build()
|
||||||
|
|
||||||
|
|
||||||
MongoClient.create(clientSettings)
|
MongoClient.create(clientSettings)
|
||||||
.getDatabase(System.getProperty("owl.broker.mongo.database", "mongo-test"))
|
.getDatabase(System.getProperty("owl.broker.mongo.database", "mongo-test"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,11 @@ class MongodbConsumerRepository(database: MongoDatabase) : ConsumerRepository {
|
||||||
|
|
||||||
private val collection = database.getCollection<ConsumerMongodb>("consumers")
|
private val collection = database.getCollection<ConsumerMongodb>("consumers")
|
||||||
override suspend fun save(consumer: Consumer): Consumer = withContext(Dispatchers.IO) {
|
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
|
return@withContext consumer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,15 +53,19 @@ data class ConsumerMongodb(
|
||||||
val name: String,
|
val name: String,
|
||||||
val hostname: String,
|
val hostname: String,
|
||||||
val tasks: List<String>
|
val tasks: List<String>
|
||||||
){
|
) {
|
||||||
|
|
||||||
fun toConsumer():Consumer{
|
fun toConsumer(): Consumer {
|
||||||
return 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(
|
return ConsumerMongodb(
|
||||||
consumer.id.toString(),
|
consumer.id.toString(),
|
||||||
consumer.name,
|
consumer.name,
|
||||||
|
|
|
@ -48,7 +48,8 @@ class MongodbQueuedTaskRepository(
|
||||||
override suspend fun save(queuedTask: QueuedTask): QueuedTask {
|
override suspend fun save(queuedTask: QueuedTask): QueuedTask {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
collection.replaceOne(
|
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)
|
ReplaceOptions().upsert(true)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -57,7 +58,6 @@ class MongodbQueuedTaskRepository(
|
||||||
|
|
||||||
override suspend fun findByTaskIdAndAssignedConsumerIsNullAndUpdate(id: UUID, update: QueuedTask): QueuedTask {
|
override suspend fun findByTaskIdAndAssignedConsumerIsNullAndUpdate(id: UUID, update: QueuedTask): QueuedTask {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
|
|
||||||
val findOneAndUpdate = collection.findOneAndUpdate(
|
val findOneAndUpdate = collection.findOneAndUpdate(
|
||||||
and(
|
and(
|
||||||
eq("_id", id.toString()),
|
eq("_id", id.toString()),
|
||||||
|
@ -108,7 +108,7 @@ data class QueuedTaskMongodb(
|
||||||
val task: TaskMongodb,
|
val task: TaskMongodb,
|
||||||
val attempt: Int,
|
val attempt: Int,
|
||||||
val queuedAt: Instant,
|
val queuedAt: Instant,
|
||||||
val priority:Int,
|
val priority: Int,
|
||||||
val isActive: Boolean,
|
val isActive: Boolean,
|
||||||
val timeoutAt: Instant?,
|
val timeoutAt: Instant?,
|
||||||
val assignedConsumer: String?,
|
val assignedConsumer: String?,
|
||||||
|
@ -155,14 +155,14 @@ data class QueuedTaskMongodb(
|
||||||
companion object {
|
companion object {
|
||||||
fun of(propertySerializerFactory: PropertySerializerFactory, task: Task): TaskMongodb {
|
fun of(propertySerializerFactory: PropertySerializerFactory, task: Task): TaskMongodb {
|
||||||
return TaskMongodb(
|
return TaskMongodb(
|
||||||
task.name,
|
name = task.name,
|
||||||
task.id.toString(),
|
id = task.id.toString(),
|
||||||
task.publishProducerId.toString(),
|
publishProducerId = task.publishProducerId.toString(),
|
||||||
task.publishedAt,
|
publishedAt = task.publishedAt,
|
||||||
task.nextRetry,
|
nextRetry = task.nextRetry,
|
||||||
task.completedAt,
|
completedAt = task.completedAt,
|
||||||
task.attempt,
|
attempt = task.attempt,
|
||||||
PropertySerializeUtils.serialize(propertySerializerFactory, task.properties)
|
properties = PropertySerializeUtils.serialize(propertySerializerFactory, task.properties)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,15 +171,15 @@ data class QueuedTaskMongodb(
|
||||||
companion object {
|
companion object {
|
||||||
fun of(propertySerializerFactory: PropertySerializerFactory, queuedTask: QueuedTask): QueuedTaskMongodb {
|
fun of(propertySerializerFactory: PropertySerializerFactory, queuedTask: QueuedTask): QueuedTaskMongodb {
|
||||||
return QueuedTaskMongodb(
|
return QueuedTaskMongodb(
|
||||||
queuedTask.task.id.toString(),
|
id = queuedTask.task.id.toString(),
|
||||||
TaskMongodb.of(propertySerializerFactory, queuedTask.task),
|
task = TaskMongodb.of(propertySerializerFactory, queuedTask.task),
|
||||||
queuedTask.attempt,
|
attempt = queuedTask.attempt,
|
||||||
queuedTask.queuedAt,
|
queuedAt = queuedTask.queuedAt,
|
||||||
queuedTask.priority,
|
priority = queuedTask.priority,
|
||||||
queuedTask.isActive,
|
isActive = queuedTask.isActive,
|
||||||
queuedTask.timeoutAt,
|
timeoutAt = queuedTask.timeoutAt,
|
||||||
queuedTask.assignedConsumer?.toString(),
|
assignedConsumer = queuedTask.assignedConsumer?.toString(),
|
||||||
queuedTask.assignedAt
|
assignedAt = queuedTask.assignedAt
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ class MongodbTaskDefinitionRepository(database: MongoDatabase) : TaskDefinitionR
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun deleteByName(name: String): Unit = withContext(Dispatchers.IO) {
|
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) {
|
override suspend fun findByName(name: String): TaskDefinition? = withContext(Dispatchers.IO) {
|
||||||
|
|
|
@ -36,27 +36,29 @@ import org.bson.codecs.pojo.annotations.BsonRepresentation
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
class MongodbTaskRepository(database: MongoDatabase, private val propertySerializerFactory: PropertySerializerFactory) :
|
class MongodbTaskRepository(database: MongoDatabase, private val propertySerializerFactory: PropertySerializerFactory) :
|
||||||
TaskRepository {
|
TaskRepository {
|
||||||
|
|
||||||
private val collection = database.getCollection<TaskMongodb>("tasks")
|
private val collection = database.getCollection<TaskMongodb>("tasks")
|
||||||
override suspend fun save(task: Task): Task = withContext(Dispatchers.IO) {
|
override suspend fun save(task: Task): Task = withContext(Dispatchers.IO) {
|
||||||
collection.replaceOne(
|
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)
|
ReplaceOptions().upsert(true)
|
||||||
)
|
)
|
||||||
return@withContext task
|
return@withContext task
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun saveAll(tasks: List<Task>): Unit = withContext(Dispatchers.IO) {
|
override suspend fun saveAll(tasks: List<Task>): Unit = withContext(Dispatchers.IO) {
|
||||||
collection.bulkWrite(tasks.map {
|
collection.bulkWrite(
|
||||||
|
tasks.map {
|
||||||
ReplaceOneModel(
|
ReplaceOneModel(
|
||||||
Filters.eq(it.id.toString()),
|
Filters.eq(it.id.toString()),
|
||||||
TaskMongodb.of(propertySerializerFactory, it),
|
TaskMongodb.of(propertySerializerFactory, it),
|
||||||
ReplaceOptions().upsert(true)
|
ReplaceOptions().upsert(true)
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findByNextRetryBeforeAndCompletedAtIsNull(timestamp: Instant): Flow<Task> {
|
override fun findByNextRetryBeforeAndCompletedAtIsNull(timestamp: Instant): Flow<Task> {
|
||||||
|
@ -75,12 +77,13 @@ class MongodbTaskRepository(database: MongoDatabase, private val propertySeriali
|
||||||
|
|
||||||
override suspend fun findByIdAndUpdate(id: UUID, task: Task) {
|
override suspend fun findByIdAndUpdate(id: UUID, task: Task) {
|
||||||
collection.replaceOne(
|
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)
|
ReplaceOptions().upsert(false)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findByPublishProducerIdAndCompletedAtIsNotNull(publishProducerId: UUID): Flow<Task> {
|
override fun findByPublishProducerIdAndCompletedAtIsNotNull(publishProducerId: UUID): Flow<Task> {
|
||||||
return collection
|
return collection
|
||||||
.find(Filters.eq(TaskMongodb::publishProducerId.name, publishProducerId.toString()))
|
.find(Filters.eq(TaskMongodb::publishProducerId.name, publishProducerId.toString()))
|
||||||
.map { it.toTask(propertySerializerFactory) }
|
.map { it.toTask(propertySerializerFactory) }
|
||||||
|
@ -116,14 +119,14 @@ data class TaskMongodb(
|
||||||
companion object {
|
companion object {
|
||||||
fun of(propertySerializerFactory: PropertySerializerFactory, task: Task): TaskMongodb {
|
fun of(propertySerializerFactory: PropertySerializerFactory, task: Task): TaskMongodb {
|
||||||
return TaskMongodb(
|
return TaskMongodb(
|
||||||
task.name,
|
name = task.name,
|
||||||
task.id.toString(),
|
id = task.id.toString(),
|
||||||
task.publishProducerId.toString(),
|
publishProducerId = task.publishProducerId.toString(),
|
||||||
task.publishedAt,
|
publishedAt = task.publishedAt,
|
||||||
task.nextRetry,
|
nextRetry = task.nextRetry,
|
||||||
task.completedAt,
|
completedAt = task.completedAt,
|
||||||
task.attempt,
|
attempt = task.attempt,
|
||||||
PropertySerializeUtils.serialize(propertySerializerFactory, task.properties)
|
properties = PropertySerializeUtils.serialize(propertySerializerFactory, task.properties)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,14 +41,17 @@ class MongodbTaskResultRepository(
|
||||||
private val collection = database.getCollection<TaskResultMongodb>("task_results")
|
private val collection = database.getCollection<TaskResultMongodb>("task_results")
|
||||||
override suspend fun save(taskResult: TaskResult): TaskResult = withContext(Dispatchers.IO) {
|
override suspend fun save(taskResult: TaskResult): TaskResult = withContext(Dispatchers.IO) {
|
||||||
collection.replaceOne(
|
collection.replaceOne(
|
||||||
Filters.eq(taskResult.id.toString()), TaskResultMongodb.of(propertySerializerFactory, taskResult),
|
Filters.eq(taskResult.id.toString()),
|
||||||
|
TaskResultMongodb.of(propertySerializerFactory, taskResult),
|
||||||
ReplaceOptions().upsert(true)
|
ReplaceOptions().upsert(true)
|
||||||
)
|
)
|
||||||
return@withContext taskResult
|
return@withContext taskResult
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findByTaskId(id: UUID): Flow<TaskResult> {
|
override fun findByTaskId(id: UUID): Flow<TaskResult> {
|
||||||
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 {
|
fun toTaskResult(propertySerializerFactory: PropertySerializerFactory): TaskResult {
|
||||||
return TaskResult(
|
return TaskResult(
|
||||||
UUID.fromString(id),
|
id = UUID.fromString(id),
|
||||||
UUID.fromString(taskId),
|
taskId = UUID.fromString(taskId),
|
||||||
success,
|
success = success,
|
||||||
attempt,
|
attempt = attempt,
|
||||||
PropertySerializeUtils.deserialize(propertySerializerFactory, result),
|
result = PropertySerializeUtils.deserialize(propertySerializerFactory, result),
|
||||||
message
|
message = message
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun of(propertySerializerFactory: PropertySerializerFactory, taskResult: TaskResult): TaskResultMongodb {
|
fun of(propertySerializerFactory: PropertySerializerFactory, taskResult: TaskResult): TaskResultMongodb {
|
||||||
return TaskResultMongodb(
|
return TaskResultMongodb(
|
||||||
taskResult.id.toString(),
|
id = taskResult.id.toString(),
|
||||||
taskResult.taskId.toString(),
|
taskId = taskResult.taskId.toString(),
|
||||||
taskResult.success,
|
success = taskResult.success,
|
||||||
taskResult.attempt,
|
attempt = taskResult.attempt,
|
||||||
PropertySerializeUtils.serialize(propertySerializerFactory, taskResult.result),
|
result = PropertySerializeUtils.serialize(propertySerializerFactory, taskResult.result),
|
||||||
taskResult.message
|
message = taskResult.message
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,12 +7,14 @@ import dev.usbharu.owl.broker.domain.model.consumer.Consumer
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.bson.UuidRepresentation
|
import org.bson.UuidRepresentation
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Disabled
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
class MongodbConsumerRepositoryTest {
|
class MongodbConsumerRepositoryTest {
|
||||||
@Test
|
@Test
|
||||||
|
@Disabled
|
||||||
fun name() {
|
fun name() {
|
||||||
|
|
||||||
val clientSettings =
|
val clientSettings =
|
||||||
|
|
|
@ -90,7 +90,6 @@ fun main() {
|
||||||
|
|
||||||
logger.info("Use module name: {}", moduleContext)
|
logger.info("Use module name: {}", moduleContext)
|
||||||
|
|
||||||
|
|
||||||
val koin = startKoin {
|
val koin = startKoin {
|
||||||
printLogger()
|
printLogger()
|
||||||
|
|
||||||
|
@ -98,7 +97,6 @@ fun main() {
|
||||||
single<RetryPolicyFactory> {
|
single<RetryPolicyFactory> {
|
||||||
DefaultRetryPolicyFactory(mapOf("" to ExponentialRetryPolicy()))
|
DefaultRetryPolicyFactory(mapOf("" to ExponentialRetryPolicy()))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
modules(mainModule, module, moduleContext.module())
|
modules(mainModule, module, moduleContext.module())
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,11 +19,9 @@ package dev.usbharu.owl.broker
|
||||||
import org.koin.core.module.Module
|
import org.koin.core.module.Module
|
||||||
|
|
||||||
interface ModuleContext {
|
interface ModuleContext {
|
||||||
fun module():Module
|
fun module(): Module
|
||||||
}
|
}
|
||||||
|
|
||||||
data object EmptyModuleContext : ModuleContext {
|
data object EmptyModuleContext : ModuleContext {
|
||||||
override fun module(): Module {
|
override fun module(): Module = org.koin.dsl.module { }
|
||||||
return org.koin.dsl.module { }
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -38,7 +38,7 @@ class OwlBrokerApplication(
|
||||||
|
|
||||||
private lateinit var server: Server
|
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)
|
server = ServerBuilder.forPort(port)
|
||||||
.addService(assignmentTaskService)
|
.addService(assignmentTaskService)
|
||||||
.addService(definitionTaskService)
|
.addService(definitionTaskService)
|
||||||
|
@ -64,5 +64,4 @@ class OwlBrokerApplication(
|
||||||
fun stop() {
|
fun stop() {
|
||||||
server.shutdown()
|
server.shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -19,7 +19,7 @@ package dev.usbharu.owl.broker.domain.model.consumer
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
interface ConsumerRepository {
|
interface ConsumerRepository {
|
||||||
suspend fun save(consumer: Consumer):Consumer
|
suspend fun save(consumer: Consumer): Consumer
|
||||||
|
|
||||||
suspend fun findById(id:UUID):Consumer?
|
suspend fun findById(id: UUID): Consumer?
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue