chore: repair stale test suite, guard nil config, add fork roadmap

- docs: ROADMAP.md — full analysis of upstream state, security posture,
  issue/PR triage, Pro feature map, desktop+android port plan
- pkg/request: nil-guard config access in Request() — NewClientDeprecated
  leaves config nil, panicking GeneralClient and callers
- pkg/request/request_test.go: WithSlaveMeta takes int, drop dead cache call
- pkg/cache: update memo/redis tests to current constructor + Delete(prefix, keys...)
- Remove driver_test.go, conf_test.go — they exercise v3 APIs deleted upstream
- go.mod: mholt/archives v0.1.5 (rardecode 2.2.0 compat)

Authored By: TDvorak <info@tdvorak.dev>
pull/3579/head
Tomas Dvorak 2 weeks ago
parent 6f1251ab20
commit aff2ffa156

@ -0,0 +1,160 @@
# Cloudreve Fork — Analysis & Roadmap
Fork: `Dvorinka/cloudreve` · Upstream: `cloudreve/cloudreve` · Baseline: `4.19.1` (exact upstream HEAD, zero divergence)
Original work by the Cloudreve authors (cloudreve.org). This fork continues it as a fully open-source project — every "Pro" feature reimplemented and free, desktop client on all platforms, native Android app.
---
## 1. Analysis
### 1.1 Repo state
| Fact | Value |
|---|---|
| Fork vs upstream | `0 ahead / 0 behind` — clean mirror of `master` @ 4.19.1 |
| Backend | Go 1.26, Gin, ent ORM, `go build ./...` compiles (only `assets.zip` embed fails until frontend is built — expected) |
| Frontend | `assets/` submodule → `cloudreve/frontend` (React + Vite + TS, 69 deps), not yet initialized locally |
| Storage drivers present | local, S3, OSS, COS, Qiniu, Upyun, OneDrive, remote node (`service/explorer/slave.go`) |
| Auth present | password+2FA, passkey (`ent/schema/passkey.go`), generic OAuth client/grant, WebDAV accounts (`davaccount.go`) |
### 1.2 Security posture
16 published GHSAs on upstream — **all fixed at our baseline** (vuln ranges ≤ 4.17.0, we run 4.19.1). Spot-verified in tree, not just by version:
- GHSA-f8xp (account takeover, insecure PRNG): `pkg/util/common.go` uses `crypto/rand` primary, `math/rand` only on crypto failure — fix present
- GHSA-vgj4 (OAuth scope bypass, missing client_id): `service/oauth/oauth.go:159` validates `authCode.ClientID != s.ClientID` — fix present
- Remaining highs (WebDAV path traversal, quota TOCTOU, OneDrive cred update via Admin.Read) — all ≤ 4.16.x ranges, patched
Ongoing security work is in the roadmap (§5), not the backlog.
### 1.3 Pro feature map (cloudreve.org/pricing — all 5 slides captured)
The community repo contains **zero Pro code** — Pro ships as a separate licensed binary (`--license-key`, `proupgrade` DB migration). BUT the community **frontend already contains the full Pro UI skeleton** — every surface below renders a `ProChip` badge that opens `ProDialog.tsx`. Implementation = backend endpoints + remove the chips.
| Pro slide | Features | Frontend hooks found |
|---|---|---|
| Sharing & collaboration | write/upload/delete via share link, paid share links, granular file permissions (users/groups/anonymous), anonymous upload via share, default shares for new users | `ShareSection.tsx` (group editor) |
| Storage policy mgmt | multiple policies per group, per-directory policies, load-balancer policy, file migration between policies | `SelectProvider.tsx`, `storage_policy_id` exists (single) on group |
| User & auth | multi-account switching, Logto SSO, OIDC SSO, QQ Connect, sign-up email filtering | `SSOSettings.tsx`, `SSO/` |
| Monetization (VAS) | storage plans, membership plans, redemption codes, credits | `VAS/` dir: `GroupProducts`, `StorageProducts`, `PaymentProviders`, `GiftCodes` — **full UI exists** |
| System extensions | activity/audit logs, site announcements, node selection, report abuse | `Events.tsx` (admin), `Home.tsx` |
Backend gaps are concrete: `ShareProps` = `{share_view, show_read_me}` only; `group.storage_policy_id` is single; no order/product/credit entities at all. `NavigatorCapability_CommunityPlaceholder1–9` in `pkg/filemanager/fs/dbfs/navigator.go` are the reserved capability slots Pro fills.
### 1.4 Org repo decisions
| Repo | Verdict | Reason |
|---|---|---|
| `cloudreve` (this fork) | **Keep — base** | The core |
| `frontend` | **Fork — required** | UI is source-available and already holds Pro skeleton; we need our own fork to strip gates |
| `desktop` | **Fork — port** | Tauri+React; portable core (`cloudreve-api`, `inventory`, `tasks`, `uploader`, `drive/sync`) vs Windows-only glue (`cfapi`, `shellext`, `win32_notif`) |
| `docs` | **Fork later** | Needed when we ship; low priority |
| `docker-compose` | **Fork — small** | One file we extend (add pro-less compose + dev compose) |
| `taskqueue` | **Skip** | Dead since 2024; OneDrive offload queue superseded by in-app queue |
| `remote-server` | **Skip** | Dead PHP-era remote; v4 has native remote nodes |
| `ios-feedback` | **Skip** | Tracker for closed-source iOS app; we do Android instead |
| `theme-editor`, `frontend_v2`, `v2` | **Skip** | Archived/ancient |
### 1.5 Upstream issues — 137 open, grouped
| Group | Count | Examples |
|---|---|---|
| Bug — upload/download/sync | ~25 | #3574 trash_bin_collect OOM, #3454 PG FK on upload, #3118 WebDAV 500 on large files, #3005 WebDAV fragments, #2938 upload stuck |
| Bug — WebDAV | ~8 | #2878 mount path 404 after move, #3118, #3409 read-only groups |
| Bug — DB/migration | ~8 | #3452 MySQL HeatWave, #2880 unix socket, #2934 v3→v4 sqlite, #2981 PG18 |
| Enhancement — sharing/permissions | ~15 | #3555 preview-only shares, #3390 default share visibility, #3340 upload-only folders, #3033/#3032 multi-share ops |
| Enhancement — storage policy | ~8 | #3518 encrypt on relocation, #2961 enable/disable policy, #2262 site-wide migration |
| Enhancement — auth/SSO | ~7 | #3464 OIDC, #3056 auto-OIDC, #2179 TOTP manual key, #3479 IP whitelist |
| Enhancement — download/tasks | ~10 | #3491 advanced remote download, #3259 yt-dlp, #2427 download quota, #2270 cancel tasks |
| UX/polish | ~20 | #3288 breadcrumb restore, #3223 deselect on empty click, #3508 loop video, #3507 gallery names |
| Pro-related (becomes free here) | ~10 | #3572 ADFS OIDC, #3515 recurring billing, #3180 group-expiry downgrade, #3171 subaddress ban |
| Mobile/iOS requests | ~5 | #3003 captcha incompat, #2826 login fail, #2863 capacity 0KB — Android solves |
| Chinese-titled (mixed) | ~40 | folded into groups above after translation |
| wontfix by upstream (revisit) | ~10 | #2494 multi-group, #2883 slide captcha, #2842 file audit — **candidates for us** |
### 1.6 Upstream PRs — verdicts
| PR | Change | Verdict |
|---|---|---|
| #3524 | revalidate share/direct links after permission change (+185/-1, CLEAN) — fixes #3544, same class as GHSA-vx2m | **Merge** — security |
| #3549 | gorilla/websocket 1.5.0→1.5.3 | **Merge** — dep CVE hygiene |
| #2964 | nwaples/rardecode 2.1.0→2.2.0 | **Merge** — dep hygiene |
| #2851 | ulikunitz/xz 0.5.12→0.5.14 | **Merge** — dep hygiene |
| #3490 | IP range/CIDR filter in event logs (+668, 2 files) — fixes #3480 | **Merge** — small, self-contained |
| #3472 | OIDC provider support (+292/-6, 10 files) — fixes #3464, overlaps Pro SSO | **Merge after review** — strategic (free OIDC) |
| #2481 | multi-stage Dockerfile (+18/-2) | **Review** — conflicts w/ our own docker work likely; take if clean |
| #2507 | p2p QUIC (draft) | **Skip** — draft, scope creep |
| #2499 | multi-group users (draft, WIP) | **Watch** — big feature, matches #2494 wontfix; revisit when upstream matures it |
| #1802 | checksum on WOPI/text-edit update (draft, 2024) | **Skip** — stale draft |
---
## 2. Immediate actions (this change set)
- [x] Analysis + this roadmap
- [ ] Enable Issues on fork; label taxonomy (`upstream-####`, `group:*`, `pro-free`, `desktop`, `android`, `security`)
- [ ] Migrate upstream issues → fork (translate Chinese titles, tag `upstream-NNNN` + group labels, link originals)
- [ ] Cherry-pick merge: #3524, #3549, #2964, #2851, #3490 (and #3472 after compile review)
- [ ] `go build ./...` + `go test ./...` green
## 3. Phase A — foundation hardening (first weeks)
- Sync-fork automation: weekly `upstream → fork` merge workflow (GitHub Action) so security fixes keep landing
- Dependabot/renovate on the fork
- CI: build + test + vet + frontend build on PR (upstream azure-pipelines is theirs; ours = GitHub Actions)
- `docker-compose` dev stack (postgres + app + frontend hot reload)
- Remove `ProDialog`/`ProChip` gates in frontend fork; point `assets` submodule at our frontend fork
## 4. Phase B — Pro features, free (the big one)
Order = user-visible value first; each ships with backend + UI + tests.
1. **Share collaboration** — write/upload/delete via share link, anonymous upload, share ACL (users/groups), preview-only mode (fixes #3555, #3390, #3340, #3517, #3578; uses `NavigatorCapability` placeholder slots + `ShareProps` extension + `share` entity fields)
2. **Storage policy advanced** — multiple policies per group (group→policies join table), per-directory binding, load-balancer policy, file migration between policies (fixes #3518, #2961, #2262)
3. **SSO** — generic OIDC provider (PR #3472 base), Logto connector, multi-account switching, sign-up email filtering (fixes #3464, #3056, #3505)
4. **VAS/monetization-free** — credits + redemption codes as *free* features (gift codes for admin use), storage/membership plan definitions; skip payment processor integration initially — YAGNI until a real user asks (fixes #3231)
5. **System extensions** — activity/audit log surfaced in admin, site announcements, report-abuse queue (fixes #3480, #3479 IP whitelist)
## 5. Phase C — security + quality
- Own security review on top of upstream fixes: session/token entropy audit, SSRF guard re-test (NAT64 class), rate limiting on auth endpoints
- Fix upstream bug backlog by impact: #3574 OOM (trash_bin_collect streaming), #3118/#3005 WebDAV large-file, #3454 PG FK, #3375 SMTP auth discovery
- `desloppify` + `security-reviewer` passes; scorecard appended to README
## 6. Phase D — desktop, all platforms
Goal: Windows + macOS + Linux from one Tauri codebase (`cloudreve/desktop` fork).
| Layer | Windows (exists) | macOS | Linux |
|---|---|---|---|
| Placeholders/hydration | cfapi (keep) | File Provider ext (Swift bridge) | FUSE (`fuser`) or plain sync folder |
| Shell integration | shellext (keep) | Finder sync extension | Nautilus/Dolphin plugin (later) |
| Notifications | win32_notif → replace | `tauri-plugin-notification` (all platforms) | same |
| Sync core | shared: `cloudreve-api`, `inventory`, `tasks`, `uploader`, `drive/sync` | same | same |
- Port order: (1) strip `win32_notif`→tauri notifications (all platforms benefit), (2) abstract `drive/` behind a `HydrationProvider` trait (cfapi impl on Windows, stub→FUSE on Linux, FileProvider on macOS), (3) CI matrix build all 3, (4) MSIX→also ship .dmg/.AppImage/.deb.
- Feature fallback on Linux/macOS until providers land: full sync without placeholders (download-on-access still works via sync engine).
## 7. Phase E — Android app (native, no iOS)
New repo `Dvorinka/cloudreve-android`. Kotlin + Jetpack Compose, Material 3.
- **API**: `api/v4` REST + OAuth token (entities exist: `oauthclient`, `oauthgrant`) — same surface the desktop `cloudreve-api` crate documents; port its models as the spec
- **Core features**: browse/download/upload files, share links, camera-upload (auto photo backup), offline-favorite files, local sync folder via SAF/WorkManager
- **System integration** (the "native, complete" ask): share-sheet target (upload to Cloudreve from any app), DocumentsProvider (Cloudreve in Files app), quick-share tile, notifications on share/task events
- **Auth**: webview OAuth flow → token; later passkey if backend exposes
- **WebDAV bridge**: `/dav` works as fallback file access until SDK matures
- Non-goals: iOS, tablet-first layouts (works, not optimized)
## 8. Governance
- License/credit: keep `LICENSE` (GPL-3.0), add `AUTHORS`/credit line to original Cloudreve authors in README — attribution without endorsement
- Release cadence: tag `fork-4.19.x` line first (cherry-picks only), then `5.0.0-fork` once Phase B lands
- Every merge: build + test + lint green (pre-push gate, non-negotiable)
---
## Issue migration format
Each fork issue: title translated to English when needed, body = `Upstream: cloudreve/cloudreve#NNNN` + short restatement + group labels. Epics get `epic` label and link children. Upstream `wontfix` items we want get `revisit` label.

@ -9,7 +9,7 @@ require (
github.com/Masterminds/semver/v3 v3.3.1
github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.3.0
github.com/aws/aws-sdk-go v1.34.0
github.com/bodgit/sevenzip v1.6.0
github.com/bodgit/sevenzip v1.6.1
github.com/cloudflare/cfssl v1.6.1
github.com/dhowden/tag v0.0.0-20230630033851-978a0926ee25
github.com/dsoprea/go-exif/v3 v3.0.1
@ -43,7 +43,7 @@ require (
github.com/ks3sdklib/aws-sdk-go v1.6.2
github.com/lib/pq v1.10.9
github.com/meilisearch/meilisearch-go v0.36.0
github.com/mholt/archives v0.1.3
github.com/mholt/archives v0.1.5
github.com/mojocn/base64Captcha v0.0.0-20190801020520-752b1cd608b2
github.com/pquerna/otp v1.2.0
github.com/qiniu/go-sdk/v7 v7.19.0
@ -69,9 +69,9 @@ require (
require (
ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43 // indirect
cloud.google.com/go v0.81.0 // indirect
github.com/STARRY-S/zip v0.2.1 // indirect
github.com/STARRY-S/zip v0.2.3 // indirect
github.com/agext/levenshtein v1.2.1 // indirect
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/bodgit/plumbing v1.3.0 // indirect
github.com/bodgit/windows v1.0.1 // indirect
@ -115,14 +115,14 @@ require (
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jmespath/go-jmespath v0.3.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mikelolasagasti/xz v1.0.1 // indirect
github.com/minio/minlz v1.0.0 // indirect
github.com/minio/minlz v1.0.1 // indirect
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
@ -131,16 +131,17 @@ require (
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/nwaples/rardecode/v2 v2.2.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.59.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/sorairolake/lzip-go v0.3.5 // indirect
github.com/sorairolake/lzip-go v0.3.8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
github.com/ulikunitz/xz v0.5.14 // indirect
github.com/ulikunitz/xz v0.5.15 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/zclconf/go-cty v1.8.0 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect

@ -84,6 +84,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
github.com/STARRY-S/zip v0.2.1 h1:pWBd4tuSGm3wtpoqRZZ2EAwOmcHK6XFf7bU9qcJXyFg=
github.com/STARRY-S/zip v0.2.1/go.mod h1:xNvshLODWtC4EJ702g7cTYn13G53o1+X9BWnPFpcWV4=
github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4=
github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
@ -102,6 +104,8 @@ github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.3.0 h1:wQlqotpyjYPjJz+Noh5bRu7Sn
github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.3.0/go.mod h1:FTzydeQVmR24FI0D6XWUOMKckjXehM/jgMn1xC+DA9M=
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 h1:8PmGpDEZl9yDpcdEr6Odf23feCxK3LNUNMxjXg41pZQ=
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=
@ -142,6 +146,8 @@ github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
github.com/bodgit/sevenzip v1.6.0 h1:a4R0Wu6/P1o1pP/3VV++aEOcyeBxeO/xE2Y9NSTrr6A=
github.com/bodgit/sevenzip v1.6.0/go.mod h1:zOBh9nJUof7tcrlqJFv1koWRrhz3LbDbUNngkuZxLMc=
github.com/bodgit/sevenzip v1.6.1 h1:kikg2pUMYC9ljU7W9SaqHXhym5HyKm8/M/jd31fYan4=
github.com/bodgit/sevenzip v1.6.1/go.mod h1:GVoYQbEVbOGT8n2pfqCIMRUaRjQ8F9oSqoBEqZh5fQ8=
github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=
github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
@ -621,6 +627,8 @@ github.com/kisom/goutils v1.4.3/go.mod h1:Lp5qrquG7yhYnWzZCI/68Pa/GpFynw//od6EkG
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
@ -694,6 +702,8 @@ github.com/meilisearch/meilisearch-go v0.36.0/go.mod h1:HBfHzKMxcSbTOvqdfuRA/yf6
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mholt/archives v0.1.3 h1:aEAaOtNra78G+TvV5ohmXrJOAzf++dIlYeDW3N9q458=
github.com/mholt/archives v0.1.3/go.mod h1:LUCGp++/IbV/I0Xq4SzcIR6uwgeh2yjnQWamjRQfLTU=
github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ=
github.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
@ -701,6 +711,8 @@ github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZz
github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc=
github.com/minio/minlz v1.0.0 h1:Kj7aJZ1//LlTP1DM8Jm7lNKvvJS2m74gyyXXn3+uJWQ=
github.com/minio/minlz v1.0.0/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
github.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A=
github.com/minio/minlz v1.0.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@ -786,6 +798,8 @@ github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -893,12 +907,16 @@ github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/sorairolake/lzip-go v0.3.5 h1:ms5Xri9o1JBIWvOFAorYtUNik6HI3HgBTkISiqu0Cwg=
github.com/sorairolake/lzip-go v0.3.5/go.mod h1:N0KYq5iWrMXI0ZEXKXaS9hCyOjZUQdBDEIbXfoUwbdk=
github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=
github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/speps/go-hashids v2.0.0+incompatible h1:kSfxGfESueJKTx0mpER9Y/1XHl+FVQjtCqRyYcviFbw=
github.com/speps/go-hashids v2.0.0+incompatible/go.mod h1:P7hqPzMdnZOfyIk+xrlG1QaSMw+gCBdHKsBDnhpaZvc=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
github.com/spf13/afero v1.3.4/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
@ -967,6 +985,8 @@ github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oW
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.14 h1:uv/0Bq533iFdnMHZdRBTOlaNMdb1+ZxXIlHDZHIHcvg=
github.com/ulikunitz/xz v0.5.14/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/upyun/go-sdk v2.1.0+incompatible h1:OdjXghQ/TVetWV16Pz3C1/SUpjhGBVPr+cLiqZLLyq0=
github.com/upyun/go-sdk v2.1.0+incompatible/go.mod h1:eu3F5Uz4b9ZE5bE5QsCL6mgSNWRwfj0zpJ9J626HEqs=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=

@ -1,61 +0,0 @@
package cache
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestSet(t *testing.T) {
asserts := assert.New(t)
asserts.NoError(Set("123", "321", -1))
}
func TestGet(t *testing.T) {
asserts := assert.New(t)
asserts.NoError(Set("123", "321", -1))
value, ok := Get("123")
asserts.True(ok)
asserts.Equal("321", value)
value, ok = Get("not_exist")
asserts.False(ok)
}
func TestDeletes(t *testing.T) {
asserts := assert.New(t)
asserts.NoError(Set("123", "321", -1))
err := Deletes([]string{"123"}, "")
asserts.NoError(err)
_, exist := Get("123")
asserts.False(exist)
}
func TestGetSettings(t *testing.T) {
asserts := assert.New(t)
asserts.NoError(Set("test_1", "1", -1))
values, missed := GetSettings([]string{"1", "2"}, "test_")
asserts.Equal(map[string]string{"1": "1"}, values)
asserts.Equal([]string{"2"}, missed)
}
func TestSetSettings(t *testing.T) {
asserts := assert.New(t)
err := SetSettings(map[string]string{"3": "3", "4": "4"}, "test_")
asserts.NoError(err)
value1, _ := Get("test_3")
value2, _ := Get("test_4")
asserts.Equal("3", value1)
asserts.Equal("4", value2)
}
func TestInit(t *testing.T) {
asserts := assert.New(t)
asserts.NotPanics(func() {
Init()
})
}

@ -1,6 +1,7 @@
package cache
import (
"github.com/cloudreve/Cloudreve/v4/pkg/logging"
"github.com/stretchr/testify/assert"
"testing"
"time"
@ -9,7 +10,7 @@ import (
func TestNewMemoStore(t *testing.T) {
asserts := assert.New(t)
store := NewMemoStore()
store := NewMemoStore("", logging.NewConsoleLogger(logging.LevelDebug))
asserts.NotNil(store)
asserts.NotNil(store.Store)
}
@ -17,7 +18,7 @@ func TestNewMemoStore(t *testing.T) {
func TestMemoStore_Set(t *testing.T) {
asserts := assert.New(t)
store := NewMemoStore()
store := NewMemoStore("", logging.NewConsoleLogger(logging.LevelDebug))
err := store.Set("KEY", "vAL", -1)
asserts.NoError(err)
@ -28,7 +29,7 @@ func TestMemoStore_Set(t *testing.T) {
func TestMemoStore_Get(t *testing.T) {
asserts := assert.New(t)
store := NewMemoStore()
store := NewMemoStore("", logging.NewConsoleLogger(logging.LevelDebug))
// 正常情况
{
@ -72,7 +73,7 @@ func TestMemoStore_Get(t *testing.T) {
func TestMemoStore_Gets(t *testing.T) {
asserts := assert.New(t)
store := NewMemoStore()
store := NewMemoStore("", logging.NewConsoleLogger(logging.LevelDebug))
err := store.Set("1", "1,val", -1)
err = store.Set("2", "2,val", -1)
@ -97,7 +98,7 @@ func TestMemoStore_Gets(t *testing.T) {
func TestMemoStore_Sets(t *testing.T) {
asserts := assert.New(t)
store := NewMemoStore()
store := NewMemoStore("", logging.NewConsoleLogger(logging.LevelDebug))
err := store.Sets(map[string]interface{}{
"1": "1.val",
@ -119,7 +120,7 @@ func TestMemoStore_Sets(t *testing.T) {
func TestMemoStore_Delete(t *testing.T) {
asserts := assert.New(t)
store := NewMemoStore()
store := NewMemoStore("", logging.NewConsoleLogger(logging.LevelDebug))
err := store.Sets(map[string]interface{}{
"1": "1.val",
@ -129,7 +130,7 @@ func TestMemoStore_Delete(t *testing.T) {
}, "test_")
asserts.NoError(err)
err = store.Delete([]string{"1", "2"}, "test_")
err = store.Delete("test_", "1", "2")
asserts.NoError(err)
values, miss := store.Gets([]string{"1", "2", "3", "4"}, "test_")
asserts.Equal([]string{"1", "2"}, miss)
@ -138,10 +139,10 @@ func TestMemoStore_Delete(t *testing.T) {
func TestMemoStore_GarbageCollect(t *testing.T) {
asserts := assert.New(t)
store := NewMemoStore()
store := NewMemoStore("", logging.NewConsoleLogger(logging.LevelDebug))
store.Set("test", 1, 1)
time.Sleep(time.Duration(2000) * time.Millisecond)
store.GarbageCollect()
store.GarbageCollect(logging.NewConsoleLogger(logging.LevelDebug))
_, ok := store.Get("test")
asserts.False(ok)
}

@ -3,6 +3,8 @@ package cache
import (
"errors"
"fmt"
"github.com/cloudreve/Cloudreve/v4/pkg/conf"
"github.com/cloudreve/Cloudreve/v4/pkg/logging"
"github.com/gomodule/redigo/redis"
"github.com/rafaeljusto/redigomock"
"github.com/stretchr/testify/assert"
@ -13,16 +15,16 @@ import (
func TestNewRedisStore(t *testing.T) {
asserts := assert.New(t)
store := NewRedisStore(10, "tcp", "", "", "0")
store := NewRedisStore(logging.NewConsoleLogger(logging.LevelDebug), 10, &conf.Redis{Network: "tcp", Server: "", Password: "", DB: "0"})
asserts.NotNil(store)
conn, err := store.pool.Dial()
asserts.Nil(conn)
asserts.Error(err)
asserts.Panics(func() {
store.pool.Dial()
})
testConn := redigomock.NewConn()
cmd := testConn.Command("PING").Expect("PONG")
err = store.pool.TestOnBorrow(testConn, time.Now())
err := store.pool.TestOnBorrow(testConn, time.Now())
if testConn.Stats(cmd) != 1 {
fmt.Println("Command was not used")
return
@ -291,7 +293,7 @@ func TestRedisStore_Delete(t *testing.T) {
// 正常
{
cmd := conn.Command("DEL", redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData()).ExpectSlice("OK")
err := store.Delete([]string{"1", "2", "3", "4"}, "test_")
err := store.Delete("test_", "1", "2", "3", "4")
asserts.NoError(err)
if conn.Stats(cmd) != 1 {
fmt.Println("Command was not used")
@ -303,7 +305,7 @@ func TestRedisStore_Delete(t *testing.T) {
{
conn.Clear()
cmd := conn.Command("DEL", redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData()).ExpectError(errors.New("error"))
err := store.Delete([]string{"1", "2", "3", "4"}, "test_")
err := store.Delete("test_", "1", "2", "3", "4")
asserts.Error(err)
if conn.Stats(cmd) != 1 {
fmt.Println("Command was not used")
@ -318,7 +320,7 @@ func TestRedisStore_Delete(t *testing.T) {
Dial: func() (redis.Conn, error) { return nil, errors.New("error") },
MaxIdle: 10,
}
err := store.Delete([]string{"1", "2", "3", "4"}, "test_")
err := store.Delete("test_", "1", "2", "3", "4")
asserts.Error(err)
}
}

@ -1,94 +0,0 @@
package conf
import (
"github.com/cloudreve/Cloudreve/v4/pkg/util"
"github.com/stretchr/testify/assert"
"io/ioutil"
"os"
"testing"
)
// 测试Init日志路径错误
func TestInitPanic(t *testing.T) {
asserts := assert.New(t)
// 日志路径不存在时
asserts.NotPanics(func() {
Init("not/exist/path")
})
asserts.True(util.Exists("conf.ini"))
}
// TestInitDelimiterNotFound 日志路径存在但 Key 格式错误时
func TestInitDelimiterNotFound(t *testing.T) {
asserts := assert.New(t)
testCase := `[Database]
Type = mysql
User = root
Password233root
Host = 127.0.0.1:3306
Name = v3
TablePrefix = v3_`
err := ioutil.WriteFile("testConf.ini", []byte(testCase), 0644)
defer func() { err = os.Remove("testConf.ini") }()
if err != nil {
panic(err)
}
asserts.Panics(func() {
Init("testConf.ini")
})
}
// TestInitNoPanic 日志路径存在且合法时
func TestInitNoPanic(t *testing.T) {
asserts := assert.New(t)
testCase := `
[System]
Listen = 3000
HashIDSalt = 1
[Database]
Type = mysql
User = root
Password = root
Host = 127.0.0.1:3306
Name = v3
TablePrefix = v3_`
err := ioutil.WriteFile("testConf.ini", []byte(testCase), 0644)
defer func() { err = os.Remove("testConf.ini") }()
if err != nil {
panic(err)
}
asserts.NotPanics(func() {
Init("testConf.ini")
})
}
func TestMapSection(t *testing.T) {
asserts := assert.New(t)
//正常情况
testCase := `
[System]
Listen = 3000
HashIDSalt = 1
[Database]
Type = mysql
User = root
Password:root
Host = 127.0.0.1:3306
Name = v3
TablePrefix = v3_`
err := ioutil.WriteFile("testConf.ini", []byte(testCase), 0644)
defer func() { err = os.Remove("testConf.ini") }()
if err != nil {
panic(err)
}
Init("testConf.ini")
err = mapSection("Database", DatabaseConfig)
asserts.NoError(err)
}

@ -156,8 +156,7 @@ func (c *HTTPClient) Request(method, target string, body io.Reader, opts ...Opti
req.Header.Add(CorrelationHeader, logging.CorrelationID(options.ctx).String())
}
mode := c.config.System().Mode
if options.masterMeta && mode == conf.MasterMode {
if c.config != nil && options.masterMeta && c.config.System().Mode == conf.MasterMode {
req.Header.Add(SiteURLHeader, options.siteURL)
req.Header.Add(SiteIDHeader, options.siteID)
req.Header.Add(SiteVersionHeader, constants.BackendVersion)

@ -4,7 +4,6 @@ import (
"context"
"errors"
"github.com/cloudreve/Cloudreve/v4/pkg/auth"
"github.com/cloudreve/Cloudreve/v4/pkg/cache"
"github.com/stretchr/testify/assert"
testMock "github.com/stretchr/testify/mock"
"io"
@ -54,7 +53,7 @@ func TestWithContext(t *testing.T) {
func TestHTTPClient_Request(t *testing.T) {
asserts := assert.New(t)
client := NewClientDeprecated(WithSlaveMeta("test"))
client := NewClientDeprecated(WithSlaveMeta(1))
// 正常
{
@ -230,7 +229,6 @@ func TestNopRSCloser_SetFirstFakeChunk(t *testing.T) {
func TestBlackHole(t *testing.T) {
a := assert.New(t)
cache.Set("setting_reset_after_upload_failed", "true", 0)
a.NotPanics(func() {
BlackHole(strings.NewReader("TestBlackHole"))
})

Loading…
Cancel
Save