feat/refactor #5
7
Makefile
7
Makefile
|
@ -49,12 +49,13 @@ build-docker:
|
||||||
.PHONY: mock-gen
|
.PHONY: mock-gen
|
||||||
mock-gen: # 建立 mock 資料
|
mock-gen: # 建立 mock 資料
|
||||||
mockgen -source=./pkg/domain/repository/token.go -destination=./pkg/mock/repository/token.go -package=mock
|
mockgen -source=./pkg/domain/repository/token.go -destination=./pkg/mock/repository/token.go -package=mock
|
||||||
|
mockgen -source=./pkg/domain/repository/permission.go -destination=./pkg/mock/repository/permission.go -package=mock
|
||||||
|
mockgen -source=./pkg/domain/repository/role.go -destination=./pkg/mock/repository/role.go -package=mock
|
||||||
|
mockgen -source=./pkg/domain/repository/role_permission.go -destination=./pkg/mock/repository/role_permission.go -package=mock
|
||||||
|
mockgen -source=./pkg/domain/repository/user_role.go -destination=./pkg/mock/repository/user_role.go -package=mock
|
||||||
@echo "Generate mock files successfully"
|
@echo "Generate mock files successfully"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@echo "Generate mock files successfully"
|
|
||||||
|
|
||||||
.PHONY: migrate-database
|
.PHONY: migrate-database
|
||||||
migrate-database:
|
migrate-database:
|
||||||
migrate -source file://generate/database/mysql -database 'mysql://root:yytt@tcp(127.0.0.1:3306)/digimon_permission' up
|
migrate -source file://generate/database/mysql -database 'mysql://root:yytt@tcp(127.0.0.1:3306)/digimon_permission' up
|
||||||
|
|
33
go.mod
33
go.mod
|
@ -10,6 +10,7 @@ require (
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.1
|
github.com/golang-jwt/jwt/v4 v4.5.1
|
||||||
github.com/segmentio/ksuid v1.0.4
|
github.com/segmentio/ksuid v1.0.4
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
|
github.com/testcontainers/testcontainers-go v0.34.0
|
||||||
github.com/zeromicro/go-zero v1.8.0
|
github.com/zeromicro/go-zero v1.8.0
|
||||||
go.mongodb.org/mongo-driver v1.17.2
|
go.mongodb.org/mongo-driver v1.17.2
|
||||||
go.uber.org/mock v0.5.0
|
go.uber.org/mock v0.5.0
|
||||||
|
@ -18,20 +19,33 @@ require (
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
dario.cat/mergo v1.0.0 // indirect
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect
|
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect
|
github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect
|
||||||
github.com/casbin/govaluate v1.3.0 // indirect
|
github.com/casbin/govaluate v1.3.0 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/containerd/containerd v1.7.18 // indirect
|
||||||
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
|
github.com/containerd/platforms v0.2.1 // indirect
|
||||||
github.com/coreos/go-semver v0.3.1 // indirect
|
github.com/coreos/go-semver v0.3.1 // indirect
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||||
|
github.com/cpuguy83/dockercfg v0.3.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/distribution/reference v0.6.0 // indirect
|
||||||
|
github.com/docker/docker v27.1.1+incompatible // indirect
|
||||||
|
github.com/docker/go-connections v0.5.0 // indirect
|
||||||
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||||
github.com/fatih/color v1.18.0 // indirect
|
github.com/fatih/color v1.18.0 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||||
github.com/go-openapi/swag v0.22.4 // indirect
|
github.com/go-openapi/swag v0.22.4 // indirect
|
||||||
|
@ -47,32 +61,51 @@ require (
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.17.11 // indirect
|
github.com/klauspost/compress v1.17.11 // indirect
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||||
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||||
|
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||||
|
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||||
|
github.com/moby/sys/user v0.1.0 // indirect
|
||||||
|
github.com/moby/term v0.5.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||||
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
|
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||||
github.com/openzipkin/zipkin-go v0.4.3 // indirect
|
github.com/openzipkin/zipkin-go v0.4.3 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||||
github.com/prometheus/client_golang v1.20.5 // indirect
|
github.com/prometheus/client_golang v1.20.5 // indirect
|
||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
github.com/prometheus/common v0.55.0 // indirect
|
github.com/prometheus/common v0.55.0 // indirect
|
||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
github.com/redis/go-redis/v9 v9.7.0 // indirect
|
github.com/redis/go-redis/v9 v9.7.0 // indirect
|
||||||
|
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
github.com/shopspring/decimal v1.4.0 // indirect
|
github.com/shopspring/decimal v1.4.0 // indirect
|
||||||
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||||
|
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||||
github.com/xdg-go/scram v1.1.2 // indirect
|
github.com/xdg-go/scram v1.1.2 // indirect
|
||||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||||
github.com/yuin/gopher-lua v1.1.1 // indirect
|
github.com/yuin/gopher-lua v1.1.1 // indirect
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||||
go.etcd.io/etcd/api/v3 v3.5.15 // indirect
|
go.etcd.io/etcd/api/v3 v3.5.15 // indirect
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect
|
go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect
|
||||||
go.etcd.io/etcd/client/v3 v3.5.15 // indirect
|
go.etcd.io/etcd/client/v3 v3.5.15 // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.34.0 // indirect
|
go.opentelemetry.io/otel v1.34.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
|
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect
|
||||||
|
|
81
go.sum
81
go.sum
|
@ -2,6 +2,14 @@ code.30cm.net/digimon/library-go/errs v1.2.14 h1:Un9wcIIjjJW8D2i0ISf8ibzp9oNT4Oq
|
||||||
code.30cm.net/digimon/library-go/errs v1.2.14/go.mod h1:Hs4v7SbXNggDVBGXSYsFMjkii1qLF+rugrIpWePN4/o=
|
code.30cm.net/digimon/library-go/errs v1.2.14/go.mod h1:Hs4v7SbXNggDVBGXSYsFMjkii1qLF+rugrIpWePN4/o=
|
||||||
code.30cm.net/digimon/library-go/mongo v0.0.9 h1:fPciIE5B85tXpLg8aeVQqKVbLnfpVAk9xbMu7pE2tVw=
|
code.30cm.net/digimon/library-go/mongo v0.0.9 h1:fPciIE5B85tXpLg8aeVQqKVbLnfpVAk9xbMu7pE2tVw=
|
||||||
code.30cm.net/digimon/library-go/mongo v0.0.9/go.mod h1:KBVKz/Ci5IheI77BgZxPUeKkaGvDy8fV8EDHSCOLIO4=
|
code.30cm.net/digimon/library-go/mongo v0.0.9/go.mod h1:KBVKz/Ci5IheI77BgZxPUeKkaGvDy8fV8EDHSCOLIO4=
|
||||||
|
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||||
|
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||||
|
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
|
||||||
|
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE=
|
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE=
|
||||||
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||||
github.com/alicebob/miniredis/v2 v2.34.0 h1:mBFWMaJSNL9RwdGRyEDoAAv8OQc5UlEhLDQggTglU/0=
|
github.com/alicebob/miniredis/v2 v2.34.0 h1:mBFWMaJSNL9RwdGRyEDoAAv8OQc5UlEhLDQggTglU/0=
|
||||||
|
@ -24,26 +32,48 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao=
|
||||||
|
github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4=
|
||||||
|
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||||
|
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||||
|
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
|
||||||
|
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
|
||||||
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
||||||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
|
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
|
||||||
|
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||||
|
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
|
github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
|
||||||
|
github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
|
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||||
|
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||||
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||||
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
||||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||||
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||||
|
@ -67,6 +97,7 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
||||||
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
|
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
|
||||||
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
@ -98,6 +129,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||||
|
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
|
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
@ -105,6 +140,16 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||||
|
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||||
|
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||||
|
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||||
|
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
||||||
|
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
|
||||||
|
github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg=
|
||||||
|
github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU=
|
||||||
|
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||||
|
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
@ -112,12 +157,18 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
|
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
|
||||||
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||||
|
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||||
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
|
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
|
||||||
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
|
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
|
||||||
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
||||||
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
|
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||||
|
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||||
github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg=
|
github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg=
|
||||||
github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c=
|
github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||||
|
@ -126,6 +177,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||||
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||||
|
@ -142,8 +195,16 @@ github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
|
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
|
||||||
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
|
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
|
||||||
|
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
|
||||||
|
github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||||
|
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||||
|
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||||
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
@ -154,6 +215,7 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE
|
||||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
@ -161,6 +223,12 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/testcontainers/testcontainers-go v0.34.0 h1:5fbgF0vIN5u+nD3IWabQwRybuB4GY8G2HHgCkbMzMHo=
|
||||||
|
github.com/testcontainers/testcontainers-go v0.34.0/go.mod h1:6P/kMkQe8yqPHfPWNulFGdFHTD8HB2vLq/231xY2iPQ=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||||
|
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||||
|
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||||
|
@ -175,6 +243,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||||
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
github.com/zeromicro/go-zero v1.8.0 h1:4g/8VW+fOyM51HZYPeI3mXIZdEX+Fl6SsdYX2H5PYw4=
|
github.com/zeromicro/go-zero v1.8.0 h1:4g/8VW+fOyM51HZYPeI3mXIZdEX+Fl6SsdYX2H5PYw4=
|
||||||
github.com/zeromicro/go-zero v1.8.0/go.mod h1:xDBF+/iDzj30zPvu6HNUIbpz1J6+/g3Sx9D/DytJfss=
|
github.com/zeromicro/go-zero v1.8.0/go.mod h1:xDBF+/iDzj30zPvu6HNUIbpz1J6+/g3Sx9D/DytJfss=
|
||||||
go.etcd.io/etcd/api/v3 v3.5.15 h1:3KpLJir1ZEBrYuV2v+Twaa/e2MdDCEZ/70H+lzEiwsk=
|
go.etcd.io/etcd/api/v3 v3.5.15 h1:3KpLJir1ZEBrYuV2v+Twaa/e2MdDCEZ/70H+lzEiwsk=
|
||||||
|
@ -187,6 +257,8 @@ go.mongodb.org/mongo-driver v1.17.2 h1:gvZyk8352qSfzyZ2UMWcpDpMSGEr1eqE4T793Sqyh
|
||||||
go.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
go.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||||
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
||||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
||||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4=
|
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4=
|
||||||
|
@ -254,15 +326,22 @@ golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
@ -311,6 +390,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||||
|
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||||
k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw=
|
k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw=
|
||||||
k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80=
|
k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80=
|
||||||
k8s.io/apimachinery v0.29.4 h1:RaFdJiDmuKs/8cm1M6Dh1Kvyh59YQFDcFuFTSmXes6Q=
|
k8s.io/apimachinery v0.29.4 h1:RaFdJiDmuKs/8cm1M6Dh1Kvyh59YQFDcFuFTSmXes6Q=
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
package entity
|
|
||||||
|
|
||||||
import (
|
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
||||||
)
|
|
||||||
|
|
||||||
//TODO 未來才實作(要做IAM 時)
|
|
||||||
|
|
||||||
// Policy 表示一個存取控制策略文件
|
|
||||||
type Policy struct {
|
|
||||||
ID primitive.ObjectID `bson:"_id,omitempty"` // MongoDB 的主鍵
|
|
||||||
Name string `bson:"name"` // 策略名稱
|
|
||||||
Description string `bson:"description,omitempty"` // 策略描述
|
|
||||||
Effect string `bson:"effect"` // "allow" 或 "deny"
|
|
||||||
Condition interface{} `bson:"condition,omitempty"` // 存放 JSON 條件,可以是 map[string]interface{} 或其他符合 BSON 格式的資料
|
|
||||||
CreatedAt int64 `bson:"created_at"` // 建立時間
|
|
||||||
UpdatedAt int64 `bson:"updated_at"` // 更新時間
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resource 表示需要保護的資源
|
|
||||||
type Resource struct {
|
|
||||||
ID primitive.ObjectID `bson:"_id,omitempty"` // MongoDB 的主鍵
|
|
||||||
Name string `bson:"name"` // 資源名稱
|
|
||||||
Type string `bson:"type"` // 資源類型
|
|
||||||
Description string `bson:"description,omitempty"` // 資源描述
|
|
||||||
CreatedAt int64 `bson:"created_at"` // 建立時間
|
|
||||||
UpdatedAt int64 `bson:"updated_at"` // 更新時間
|
|
||||||
}
|
|
|
@ -5,9 +5,30 @@ import (
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//
|
||||||
|
//1. UID (使用者ID)
|
||||||
|
//
|
||||||
|
//這個欄位通常代表創建這個角色的使用者,可能的用途:
|
||||||
|
//• 追蹤角色的建立者:確保知道誰創建了這個角色。
|
||||||
|
//• 限定角色的管理範圍:例如,只有 UID 對應的使用者可以修改或刪除這個角色。
|
||||||
|
//• 支援多租戶 (Multi-Tenancy):如果一個 UID 只能看到自己創建的角色,那這個系統可能是多租戶架構的一部分。
|
||||||
|
//
|
||||||
|
//2. ClientID (客戶端ID)
|
||||||
|
//
|
||||||
|
//這個欄位通常代表該角色所屬的應用程式或客戶端,可能的用途:
|
||||||
|
//• 多租戶架構 (Multi-Tenancy):不同 ClientID 的角色可能互相隔離,確保不同組織不會影響彼此的角色權限。
|
||||||
|
//• 對應 OAuth 2.0 或 OpenID Connect:
|
||||||
|
//• 在 OAuth2 / OIDC 的架構下,每個應用程式 (client_id) 可能有不同的角色和權限。
|
||||||
|
//• 例如,client_id 為 web-app-1 的角色與 client_id 為 mobile-app-1 的角色可能完全不同。
|
||||||
|
//• API 權限控制:
|
||||||
|
//• ClientID 可能是用來限制某些角色只能在特定應用程式中被使用,例如 Web 端和行動端的角色不同。
|
||||||
|
|
||||||
|
// Role 是這樣,如果有綁定某個 UID
|
||||||
type Role struct {
|
type Role struct {
|
||||||
ID primitive.ObjectID `bson:"_id,omitempty"`
|
ID primitive.ObjectID `bson:"_id,omitempty"`
|
||||||
Name string `bson:"name"`
|
Name string `bson:"name"` // 角色名稱
|
||||||
|
UID string `bson:"uid"`
|
||||||
|
ClientID string `bson:"client_id"`
|
||||||
Status permission.Status `bson:"status"` // 例如 1: 啟用, 0: 停用
|
Status permission.Status `bson:"status"` // 例如 1: 啟用, 0: 停用
|
||||||
CreateAt int64 `bson:"create_at"`
|
CreateAt int64 `bson:"create_at"`
|
||||||
UpdateAt int64 `bson:"update_at"`
|
UpdateAt int64 `bson:"update_at"`
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TokenServerErrorCode = 1 + iota
|
TokenServerErrorCode = 1 + iota
|
||||||
TokenServerRedisErrorCode
|
|
||||||
TokenValidateErrorCode
|
TokenValidateErrorCode
|
||||||
TokenClaimErrorCode
|
TokenClaimErrorCode
|
||||||
TokenCreateErrorCode
|
TokenCreateErrorCode
|
||||||
|
@ -19,17 +18,8 @@ const (
|
||||||
TokenCancelErrorCode
|
TokenCancelErrorCode
|
||||||
TokensCancelErrorCode
|
TokensCancelErrorCode
|
||||||
TokenGetErrorCode
|
TokenGetErrorCode
|
||||||
NewOneTokenErrorCode
|
|
||||||
DelOneTokenErrorCode
|
|
||||||
SendTooShortErrorCode
|
|
||||||
SetForgetPasswordRedisErrorCode
|
|
||||||
FailedToGetCorrectVerifyCode
|
|
||||||
SendVerifyCodeRedisErrorCode
|
|
||||||
GenerateVerifyCodeRedisErrorCode
|
|
||||||
FailedToCheckVerifyCode
|
|
||||||
AccountPlatformNotCorrectErrorCode
|
|
||||||
|
|
||||||
PermissionDeleteErrorCode
|
FailedToGetRolePermission
|
||||||
)
|
)
|
||||||
|
|
||||||
func TokenError(ec ers.ErrorCode, s ...string) *ers.LibError {
|
func TokenError(ec ers.ErrorCode, s ...string) *ers.LibError {
|
||||||
|
|
|
@ -8,20 +8,23 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ClosePermission string = "close"
|
ClosePermission StatusCode = "close"
|
||||||
OpenPermission string = "open"
|
OpenPermission StatusCode = "open"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s Status) String() string {
|
func (s Status) String() string {
|
||||||
status, ok := statusMap[s]
|
status, ok := statusMap[s]
|
||||||
if ok {
|
if ok {
|
||||||
return status
|
return string(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ClosePermission
|
return string(ClosePermission)
|
||||||
}
|
}
|
||||||
|
|
||||||
var statusMap = map[Status]string{
|
var statusMap = map[Status]StatusCode{
|
||||||
Open: OpenPermission,
|
Open: OpenPermission,
|
||||||
Close: ClosePermission,
|
Close: ClosePermission,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StatusCode string
|
||||||
|
type Permissions map[string]StatusCode
|
||||||
|
|
|
@ -6,3 +6,5 @@ const (
|
||||||
BackendUser Type = iota + 1
|
BackendUser Type = iota + 1
|
||||||
FrontendUser
|
FrontendUser
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const AdminRoleUID = "GodDog"
|
||||||
|
|
|
@ -24,7 +24,7 @@ func (rule Rule) ToString() []string {
|
||||||
rule.Field2, rule.Field3, rule.Field4, rule.Field5,
|
rule.Field2, rule.Field3, rule.Field4, rule.Field5,
|
||||||
}
|
}
|
||||||
// 移除空字串,提高效能
|
// 移除空字串,提高效能
|
||||||
var result []string
|
result := make([]string, 0, len(fields))
|
||||||
for _, field := range fields {
|
for _, field := range fields {
|
||||||
if field != "" {
|
if field != "" {
|
||||||
result = append(result, field)
|
result = append(result, field)
|
||||||
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
package rbac
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRule_ToString(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input Rule
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "完整六個欄位",
|
||||||
|
input: Rule{
|
||||||
|
PolicyType: "p",
|
||||||
|
Field0: "admin",
|
||||||
|
Field1: "data1",
|
||||||
|
Field2: "read",
|
||||||
|
Field3: "extra1",
|
||||||
|
Field4: "extra2",
|
||||||
|
Field5: "extra3",
|
||||||
|
},
|
||||||
|
expected: []string{"p", "admin", "data1", "read", "extra1", "extra2", "extra3"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "部分欄位空白",
|
||||||
|
input: Rule{
|
||||||
|
PolicyType: "p",
|
||||||
|
Field0: "admin",
|
||||||
|
Field1: "",
|
||||||
|
Field2: "read",
|
||||||
|
Field3: "",
|
||||||
|
Field4: "",
|
||||||
|
Field5: "extra3",
|
||||||
|
},
|
||||||
|
expected: []string{"p", "admin", "read", "extra3"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "只有 PolicyType",
|
||||||
|
input: Rule{PolicyType: "p"},
|
||||||
|
expected: []string{"p"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "所有欄位皆空",
|
||||||
|
input: Rule{},
|
||||||
|
expected: []string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := tt.input.ToString()
|
||||||
|
assert.Equal(t, tt.expected, result, "ToString output should match expected")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringToPolicy(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
policyType string
|
||||||
|
input []string
|
||||||
|
expected Rule
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "完整六個欄位",
|
||||||
|
policyType: "p",
|
||||||
|
input: []string{"admin", "data1", "read", "extra1", "extra2", "extra3"},
|
||||||
|
expected: Rule{
|
||||||
|
PolicyType: "p",
|
||||||
|
Field0: "admin",
|
||||||
|
Field1: "data1",
|
||||||
|
Field2: "read",
|
||||||
|
Field3: "extra1",
|
||||||
|
Field4: "extra2",
|
||||||
|
Field5: "extra3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "部分欄位空白",
|
||||||
|
policyType: "p",
|
||||||
|
input: []string{"admin", "", "read", "", "", "extra3"},
|
||||||
|
expected: Rule{
|
||||||
|
PolicyType: "p",
|
||||||
|
Field0: "admin",
|
||||||
|
Field1: "",
|
||||||
|
Field2: "read",
|
||||||
|
Field3: "",
|
||||||
|
Field4: "",
|
||||||
|
Field5: "extra3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "只有 PolicyType",
|
||||||
|
policyType: "p",
|
||||||
|
input: []string{},
|
||||||
|
expected: Rule{
|
||||||
|
PolicyType: "p",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "只有兩個欄位",
|
||||||
|
policyType: "g",
|
||||||
|
input: []string{"user", "admin"},
|
||||||
|
expected: Rule{
|
||||||
|
PolicyType: "g",
|
||||||
|
Field0: "user",
|
||||||
|
Field1: "admin",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := StringToPolicy(tt.policyType, tt.input)
|
||||||
|
assert.Equal(t, tt.expected, result, "StringToPolicy output should match expected")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,8 +18,8 @@ type PermissionRepository interface {
|
||||||
type GetPermission interface {
|
type GetPermission interface {
|
||||||
// GetAll 取得所有權限列表
|
// GetAll 取得所有權限列表
|
||||||
GetAll(ctx context.Context, status *permission.Status) ([]entity.Permission, error)
|
GetAll(ctx context.Context, status *permission.Status) ([]entity.Permission, error)
|
||||||
// GetAllByID 以權限 ID 作為鍵取得所有權限的映射
|
// GetAllIntoIDMap 以權限 ID 作為鍵取得所有權限的映射
|
||||||
GetAllByID(ctx context.Context, status *permission.Status) (map[string]entity.Permission, error)
|
GetAllIntoIDMap(ctx context.Context, status *permission.Status) (map[string]entity.Permission, error)
|
||||||
// FindOne 根據查詢條件取得單筆權限資料
|
// FindOne 根據查詢條件取得單筆權限資料
|
||||||
FindOne(ctx context.Context, query PermissionQuery) (entity.Permission, error)
|
FindOne(ctx context.Context, query PermissionQuery) (entity.Permission, error)
|
||||||
// FindByNames 根據權限名稱列表查詢權限資料
|
// FindByNames 根據權限名稱列表查詢權限資料
|
||||||
|
|
|
@ -2,41 +2,39 @@ package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/permission"
|
||||||
"context"
|
"context"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RoleRepository interface {
|
type RoleRepository interface {
|
||||||
Insert(ctx context.Context, role *entity.Role) error
|
List(ctx context.Context, param ListQuery) ([]*entity.Role, int64, error)
|
||||||
Update(ctx context.Context, role *entity.Role) error
|
GetByID(ctx context.Context, id string) (*entity.Role, error)
|
||||||
Delete(ctx context.Context, role *entity.Role) error
|
GetByUID(ctx context.Context, uid string) (*entity.Role, error)
|
||||||
List()
|
All(ctx context.Context, clientID *string) ([]*entity.Role, error)
|
||||||
ListAll()
|
Create(ctx context.Context, role *entity.Role) error
|
||||||
Get()
|
Update(ctx context.Context, data UpdateReq) error
|
||||||
GetByUID()
|
Delete(ctx context.Context, id string) error
|
||||||
|
RoleIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
type RoleRepository interface {
|
type RoleIndex interface {
|
||||||
Page(ctx context.Context, filter PageRoleFilter, page, size int) ([]entity.Role, int64, error)
|
Index20250224UP(ctx context.Context) (*mongo.Cursor, error)
|
||||||
Get(ctx context.Context, id int64) (entity.Role, error)
|
|
||||||
GetByUID(ctx context.Context, uid string) (entity.Role, error)
|
|
||||||
All(ctx context.Context, clientID int) ([]entity.Role, error)
|
|
||||||
IncrementID(ctx context.Context) (int, error)
|
|
||||||
Create(ctx context.Context, role *entity.Role) (int64, error)
|
|
||||||
Update(ctx context.Context, role *entity.Role) error
|
|
||||||
Delete(ctx context.Context, uid string) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type PageRoleFilter struct {
|
type ListQuery struct {
|
||||||
ClientID int
|
PageSize int64 // 必填
|
||||||
UID string
|
PageIndex int64 // 必填
|
||||||
Name string
|
ClientID *string
|
||||||
Permissions []string
|
UID *string
|
||||||
Status int
|
Name *string
|
||||||
|
Status *permission.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
type RolePermissionRepository interface {
|
type UpdateReq struct {
|
||||||
BindRolePermission()
|
ID string
|
||||||
UnBindRolePermission()
|
Name *string
|
||||||
GetRolePermission()
|
UID *string
|
||||||
GetByPermissionID()
|
ClientID *string
|
||||||
|
Status *permission.Status
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
||||||
|
"context"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RolePermissionRepository interface {
|
||||||
|
Get(ctx context.Context, roleID string) ([]*entity.RolePermission, error)
|
||||||
|
GetByPermissionID(ctx context.Context, permissionIDs []string) ([]*entity.RolePermission, error)
|
||||||
|
Create(ctx context.Context, entity entity.RolePermission) error
|
||||||
|
Delete(ctx context.Context, roleID string, permission string) error
|
||||||
|
RolePermissionIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
type RolePermissionIndex interface {
|
||||||
|
Index20250225UP(ctx context.Context) (*mongo.Cursor, error)
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
||||||
|
"context"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserRoleRepository interface {
|
||||||
|
GetAll(ctx context.Context) ([]*entity.UserRole, error) // `All` -> `GetAll`,更直觀
|
||||||
|
GetByUserID(ctx context.Context, uid string) (entity.UserRole, error) // `Get` -> `GetByUserID`,明確是透過 `uid` 查詢
|
||||||
|
GetUsersByRoleID(ctx context.Context, roleID string) ([]entity.UserRole, error) // `GetByRoleID` -> `GetUsersByRoleID`,更具語意
|
||||||
|
CountUsersByRole(ctx context.Context) ([]RoleUserCount, error) // `UserCount` -> `CountUsersByRole`,清楚表達它是計算使用者數量
|
||||||
|
CreateUserRole(ctx context.Context, param entity.UserRole) error // `Create` -> `CreateUserRole`,明確表示新增的是使用者與角色的關係
|
||||||
|
UpdateUserRole(ctx context.Context, uid, roleID string) (entity.UserRole, error) // `Update` -> `UpdateUserRole`,明確描述更新的是哪個對象
|
||||||
|
UserRoleIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserRoleIndex interface {
|
||||||
|
Index20250225UP(ctx context.Context) (*mongo.Cursor, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RoleUserCount struct {
|
||||||
|
RoleID string `bson:"_id"`
|
||||||
|
Count int `bson:"count"`
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package token
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAdditional_String(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input Additional
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"ID to String", ID, "id"},
|
||||||
|
{"Role to String", Role, "role"},
|
||||||
|
{"Device to String", Device, "device"},
|
||||||
|
{"UID to String", UID, "uid"},
|
||||||
|
{"Account to String", Account, "account"},
|
||||||
|
{"Scope to String", Scope, "scope"},
|
||||||
|
{"Type to String", Type, "token_type"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := tt.input.String()
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsValidAdditional(t *testing.T) {
|
||||||
|
validKeys := []Additional{
|
||||||
|
ID, Role, Device,
|
||||||
|
UID, Account, Scope, Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidKeys := []Additional{
|
||||||
|
"invalid", "unknown", "random", "test",
|
||||||
|
}
|
||||||
|
|
||||||
|
// 測試有效 Key
|
||||||
|
for _, key := range validKeys {
|
||||||
|
t.Run("ValidKey_"+key.String(), func(t *testing.T) {
|
||||||
|
assert.True(t, IsValidAdditional(key))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 測試無效 Key
|
||||||
|
for _, key := range invalidKeys {
|
||||||
|
t.Run("InvalidKey_"+string(key), func(t *testing.T) {
|
||||||
|
assert.False(t, IsValidAdditional(key))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package token
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTScope_ToString(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input TScope
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"Empty String", TScope(""), ""},
|
||||||
|
{"Simple String", TScope("read"), "read"},
|
||||||
|
{"Complex String", TScope("user:write"), "user:write"},
|
||||||
|
{"Special Characters", TScope("@dmin!"), "@dmin!"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// 使用指標調用方法
|
||||||
|
result := tt.input.ToString()
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package token
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestType_ToString(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input VerifyType
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"Empty String", VerifyType(""), ""},
|
||||||
|
{"Simple String", VerifyType("read"), "read"},
|
||||||
|
{"Complex String", VerifyType("user:write"), "user:write"},
|
||||||
|
{"Special Characters", VerifyType("@dmin!"), "@dmin!"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// 使用指標調用方法
|
||||||
|
result := tt.input.ToString()
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
var (
|
||||||
|
NotFoundError = fmt.Errorf("permission not found")
|
||||||
|
)
|
|
@ -0,0 +1,42 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/permission"
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RoleUseCase interface {
|
||||||
|
List(ctx context.Context, param ListQuery) ([]Role, int64, error)
|
||||||
|
All(ctx context.Context, clientID *string) ([]Role, error)
|
||||||
|
GetByID(ctx context.Context, id string) (*Role, error)
|
||||||
|
GetByUID(ctx context.Context, uid string) (*Role, error)
|
||||||
|
Create(ctx context.Context, role CreateRoleReq) error
|
||||||
|
Update(ctx context.Context, id string, data CreateRoleReq) error
|
||||||
|
Delete(ctx context.Context, id string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListQuery struct {
|
||||||
|
PageSize int64 // 必填
|
||||||
|
PageIndex int64 // 必填
|
||||||
|
ClientID *string
|
||||||
|
UID *string
|
||||||
|
Name *string
|
||||||
|
Status *permission.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
type Role struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"` // 角色名稱
|
||||||
|
UID string `json:"uid"`
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
Status permission.Status `json:"status"` // 例如 1: 啟用, 0: 停用
|
||||||
|
CreateAt int64 `json:"create_at"`
|
||||||
|
UpdateAt int64 `json:"update_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateRoleReq struct {
|
||||||
|
Name *string `json:"name"` // 角色名稱
|
||||||
|
UID *string `json:"uid"`
|
||||||
|
ClientID *string `json:"client_id"`
|
||||||
|
Status *permission.Status `json:"status"` // 例如 1: 啟用, 0: 停用
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/permission"
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RolePermissionUseCase interface {
|
||||||
|
Get(ctx context.Context, roleID int64) (permission.Permissions, error)
|
||||||
|
GetByRoleUID(ctx context.Context, uid string) (permission.Permissions, error)
|
||||||
|
GetByUser(ctx context.Context, uid string) (UserPermission, error)
|
||||||
|
Create(ctx context.Context, roleID int64, permissions permission.Permissions) error
|
||||||
|
Delete(ctx context.Context, roleID int64, permissions permission.Permissions) error
|
||||||
|
List(ctx context.Context, req ListQuery) (RoleResp, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserPermission struct {
|
||||||
|
RoleID string `json:"role_id"`
|
||||||
|
Permissions permission.Permissions `json:"permissions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserRoleCountResp struct {
|
||||||
|
Role
|
||||||
|
UserCount int `json:"user_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RoleResp struct {
|
||||||
|
List []UserRoleCountResp `json:"list"`
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
type UserRoleUseCase interface {
|
||||||
|
Select(ctx context.Context, filter UserRoleFilter) ([]UserRole, error)
|
||||||
|
Get(ctx context.Context, uid string) (UserRole, error)
|
||||||
|
Create(ctx context.Context, uid, roleID string) (UserRole, error)
|
||||||
|
Delete(ctx context.Context, uid, roleID string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserRole struct {
|
||||||
|
UID string `json:"uid"`
|
||||||
|
RoleID string `json:"role_id"`
|
||||||
|
CreateAt int64 `json:"create_at"`
|
||||||
|
UpdateAt int64 `json:"update_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserRoleFilter struct {
|
||||||
|
RoleID string
|
||||||
|
}
|
|
@ -123,7 +123,7 @@ func (repo *PermissionRepository) GetAll(ctx context.Context, status *permission
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *PermissionRepository) GetAllByID(ctx context.Context, status *permission.Status) (map[string]entity.Permission, error) {
|
func (repo *PermissionRepository) GetAllIntoIDMap(ctx context.Context, status *permission.Status) (map[string]entity.Permission, error) {
|
||||||
permissions, err := repo.GetAll(ctx, status)
|
permissions, err := repo.GetAll(ctx, status)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -153,7 +153,7 @@ func (repo *PermissionRepository) FindByNames(ctx context.Context, names []strin
|
||||||
result := make([]entity.Permission, 0)
|
result := make([]entity.Permission, 0)
|
||||||
// 使用 $in 操作符查詢 name 在 names 切片中的文件
|
// 使用 $in 操作符查詢 name 在 names 切片中的文件
|
||||||
filter := bson.M{"name": bson.M{"$in": names}}
|
filter := bson.M{"name": bson.M{"$in": names}}
|
||||||
err := repo.DB.GetClient().FindOne(ctx, &result, filter)
|
err := repo.DB.GetClient().Find(ctx, &result, filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []entity.Permission{}, err
|
return []entity.Permission{}, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,473 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/permission"
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/repository"
|
||||||
|
mgo "code.30cm.net/digimon/library-go/mongo"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetupTestPermissionRepository(db string) (repository.PermissionRepository, func(), error) {
|
||||||
|
h, p, tearDown, err := startMongoContainer()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conf := &mgo.Conf{
|
||||||
|
Schema: Schema,
|
||||||
|
Host: fmt.Sprintf("%s:%s", h, p),
|
||||||
|
Database: db,
|
||||||
|
MaxStaleness: 300,
|
||||||
|
MaxPoolSize: 100,
|
||||||
|
MinPoolSize: 100,
|
||||||
|
MaxConnIdleTime: 300,
|
||||||
|
Compressors: []string{},
|
||||||
|
EnableStandardReadWriteSplitMode: false,
|
||||||
|
ConnectTimeoutMs: 3000,
|
||||||
|
}
|
||||||
|
|
||||||
|
param := PermissionRepositoryParam{
|
||||||
|
Conf: conf,
|
||||||
|
}
|
||||||
|
repo := NewPermissionRepository(param)
|
||||||
|
_, _ = repo.Index20250214UP(context.Background())
|
||||||
|
|
||||||
|
return repo, tearDown, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 測試 Insert
|
||||||
|
func TestPermissionRepository_Insert(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestPermissionRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
input entity.Permission
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "成功插入",
|
||||||
|
input: entity.Permission{
|
||||||
|
Name: "test-permission",
|
||||||
|
HTTPMethod: "GET",
|
||||||
|
HTTPPath: "/test",
|
||||||
|
Status: 1,
|
||||||
|
Type: permission.BackendUser,
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := repo.Insert(context.Background(), tc.input)
|
||||||
|
if tc.expectErr {
|
||||||
|
assert.NotNil(t, err, "應該要返回錯誤")
|
||||||
|
} else {
|
||||||
|
assert.Nil(t, err, "不應該返回錯誤")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPermissionRepository_Update(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestPermissionRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
// 先插入一條測試數據
|
||||||
|
existingPermission := entity.Permission{
|
||||||
|
Name: "original-permission",
|
||||||
|
HTTPMethod: "GET",
|
||||||
|
HTTPPath: "/original",
|
||||||
|
Status: 1,
|
||||||
|
Type: permission.BackendUser,
|
||||||
|
}
|
||||||
|
err = repo.Insert(context.Background(), existingPermission)
|
||||||
|
assert.Nil(t, err, "插入初始數據失敗")
|
||||||
|
|
||||||
|
// 取得剛插入的 ID
|
||||||
|
found, err := repo.FindOne(context.Background(), repository.PermissionQuery{
|
||||||
|
HTTPMethod: existingPermission.HTTPMethod,
|
||||||
|
HTTPPath: existingPermission.HTTPPath,
|
||||||
|
})
|
||||||
|
assert.Nil(t, err, "應該能找到插入的權限")
|
||||||
|
id := found.ID.Hex()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
id string
|
||||||
|
updateReq repository.UpdatePermission
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "成功更新名稱",
|
||||||
|
id: id,
|
||||||
|
updateReq: repository.UpdatePermission{
|
||||||
|
Name: ToPointer("updated-name"),
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "成功更新 HTTP 方法",
|
||||||
|
id: id,
|
||||||
|
updateReq: repository.UpdatePermission{
|
||||||
|
HTTPMethod: ToPointer("PUT"),
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "成功更新 HTTP 路徑",
|
||||||
|
id: id,
|
||||||
|
updateReq: repository.UpdatePermission{
|
||||||
|
HTTPPath: ToPointer("/updated-path"),
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "成功更新狀態",
|
||||||
|
id: id,
|
||||||
|
updateReq: repository.UpdatePermission{
|
||||||
|
Status: ToPointer(permission.Open),
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "成功更新多個欄位",
|
||||||
|
id: id,
|
||||||
|
updateReq: repository.UpdatePermission{
|
||||||
|
Name: ToPointer("multi-updated"),
|
||||||
|
HTTPMethod: ToPointer("PATCH"),
|
||||||
|
HTTPPath: ToPointer("/multi-update"),
|
||||||
|
Status: ToPointer(permission.Close),
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "更新失敗 - 錯誤的 ObjectID",
|
||||||
|
id: "invalid",
|
||||||
|
updateReq: repository.UpdatePermission{
|
||||||
|
Name: ToPointer("invalid-update"),
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := repo.Update(context.Background(), tc.id, tc.updateReq)
|
||||||
|
if tc.expectErr {
|
||||||
|
assert.NotNil(t, err, "應該要返回錯誤")
|
||||||
|
} else {
|
||||||
|
assert.Nil(t, err, "不應該返回錯誤")
|
||||||
|
|
||||||
|
// 確保更新後的資料正確
|
||||||
|
updated, err := repo.GetAll(context.Background(), nil)
|
||||||
|
assert.Nil(t, err, "應該能找到更新後的權限")
|
||||||
|
|
||||||
|
if tc.updateReq.Name != nil {
|
||||||
|
assert.Equal(t, *tc.updateReq.Name, updated[0].Name, "名稱應該被更新")
|
||||||
|
}
|
||||||
|
if tc.updateReq.HTTPMethod != nil {
|
||||||
|
assert.Equal(t, *tc.updateReq.HTTPMethod, updated[0].HTTPMethod, "HTTP 方法應該被更新")
|
||||||
|
}
|
||||||
|
if tc.updateReq.HTTPPath != nil {
|
||||||
|
assert.Equal(t, *tc.updateReq.HTTPPath, updated[0].HTTPPath, "HTTP 路徑應該被更新")
|
||||||
|
}
|
||||||
|
if tc.updateReq.Status != nil {
|
||||||
|
assert.Equal(t, *tc.updateReq.Status, updated[0].Status, "狀態應該被更新")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 測試 Delete 方法
|
||||||
|
func TestPermissionRepository_Delete(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestPermissionRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
// 插入測試數據
|
||||||
|
permission := entity.Permission{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Name: "delete-test",
|
||||||
|
HTTPMethod: "DELETE",
|
||||||
|
HTTPPath: "/test-delete",
|
||||||
|
Status: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.Insert(context.Background(), permission)
|
||||||
|
assert.NoError(t, err, "插入測試數據時不應發生錯誤")
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
id string
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "成功刪除存在的權限",
|
||||||
|
id: permission.ID.Hex(),
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "刪除不存在的權限",
|
||||||
|
id: primitive.NewObjectID().Hex(),
|
||||||
|
expectErr: false, // MongoDB 刪除不存在的 ID 仍然不會報錯
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "刪除無效的 ObjectID",
|
||||||
|
id: "invalid-object-id",
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := repo.Delete(context.Background(), tc.id)
|
||||||
|
if tc.expectErr {
|
||||||
|
assert.Error(t, err, "應該返回錯誤")
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err, "不應該返回錯誤")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 測試 GetAll 方法
|
||||||
|
func TestPermissionRepository_GetAll(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestPermissionRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
// 插入測試數據
|
||||||
|
testPermissions := []entity.Permission{
|
||||||
|
{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Name: "read",
|
||||||
|
HTTPMethod: "GET",
|
||||||
|
HTTPPath: "/read",
|
||||||
|
Status: permission.Open,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Name: "write",
|
||||||
|
HTTPMethod: "POST",
|
||||||
|
HTTPPath: "/write",
|
||||||
|
Status: permission.Close,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range testPermissions {
|
||||||
|
err := repo.Insert(context.Background(), p)
|
||||||
|
assert.NoError(t, err, "插入測試數據時不應該發生錯誤")
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
status *permission.Status
|
||||||
|
expectLen int
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "查詢所有權限",
|
||||||
|
status: nil,
|
||||||
|
expectLen: 2,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "查詢開啟的權限",
|
||||||
|
status: ToPointer(permission.Open),
|
||||||
|
expectLen: 1,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "查詢關閉的權限",
|
||||||
|
status: ToPointer(permission.Close),
|
||||||
|
expectLen: 1,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "查詢不存在的權限狀態",
|
||||||
|
status: ToPointer(permission.Status(-1)),
|
||||||
|
expectLen: 0,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result, err := repo.GetAll(context.Background(), tc.status)
|
||||||
|
if tc.expectErr {
|
||||||
|
assert.Error(t, err, "應該返回錯誤")
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err, "不應該返回錯誤")
|
||||||
|
assert.Len(t, result, tc.expectLen, "查詢結果數量不符合預期")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 測試 GetAllIntoIDMap 方法
|
||||||
|
func TestPermissionRepository_GetAllIntoIDMap(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestPermissionRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
// 插入測試數據
|
||||||
|
testPermissions := []entity.Permission{
|
||||||
|
{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Name: "read",
|
||||||
|
HTTPMethod: "GET",
|
||||||
|
HTTPPath: "/read",
|
||||||
|
Status: permission.Open,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Name: "write",
|
||||||
|
HTTPMethod: "POST",
|
||||||
|
HTTPPath: "/write",
|
||||||
|
Status: permission.Close,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range testPermissions {
|
||||||
|
err := repo.Insert(context.Background(), p)
|
||||||
|
assert.NoError(t, err, "插入測試數據時不應該發生錯誤")
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
status *permission.Status
|
||||||
|
expectLen int
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "查詢所有權限並轉為 Map",
|
||||||
|
status: nil,
|
||||||
|
expectLen: 2,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "查詢開啟的權限並轉為 Map",
|
||||||
|
status: ToPointer(permission.Open),
|
||||||
|
expectLen: 1,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "查詢關閉的權限並轉為 Map",
|
||||||
|
status: ToPointer(permission.Close),
|
||||||
|
expectLen: 1,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "查詢不存在的權限狀態並轉為 Map",
|
||||||
|
status: ToPointer(permission.Status(-1)),
|
||||||
|
expectLen: 0,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result, err := repo.GetAllIntoIDMap(context.Background(), tc.status)
|
||||||
|
if tc.expectErr {
|
||||||
|
assert.Error(t, err, "應該返回錯誤")
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err, "不應該返回錯誤")
|
||||||
|
assert.Len(t, result, tc.expectLen, "查詢結果數量不符合預期")
|
||||||
|
|
||||||
|
// 確保 Map 的 key 是權限名稱
|
||||||
|
for key, permission := range result {
|
||||||
|
assert.Equal(t, key, permission.Name, "Map 的 Key 應該與 Name 相符")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPermissionRepository_FindByNames(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestPermissionRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
// 測試數據
|
||||||
|
testPermissions := []entity.Permission{
|
||||||
|
{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Name: "read-data",
|
||||||
|
HTTPMethod: "GET",
|
||||||
|
HTTPPath: "/data",
|
||||||
|
Status: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Name: "write-data",
|
||||||
|
HTTPMethod: "POST",
|
||||||
|
HTTPPath: "/data",
|
||||||
|
Status: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 插入測試數據
|
||||||
|
for _, perm := range testPermissions {
|
||||||
|
err := repo.Insert(context.Background(), perm)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
input []string
|
||||||
|
expectLen int
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "成功查詢單個名稱",
|
||||||
|
input: []string{"read-data"},
|
||||||
|
expectLen: 1,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "成功查詢多個名稱",
|
||||||
|
input: []string{"read-data", "write-data"},
|
||||||
|
expectLen: 2,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "查詢名稱不存在時應返回空結果",
|
||||||
|
input: []string{"unknown-permission"},
|
||||||
|
expectLen: 0,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "當查詢發生錯誤時,應返回錯誤",
|
||||||
|
input: nil, // 無效查詢
|
||||||
|
expectLen: 0,
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result, err := repo.FindByNames(context.Background(), tc.input)
|
||||||
|
|
||||||
|
if tc.expectErr {
|
||||||
|
assert.Error(t, err, "應該返回錯誤")
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err, "不應該返回錯誤")
|
||||||
|
assert.Len(t, result, tc.expectLen, "返回的數據長度應符合預期")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToPointer[T any](v T) *T {
|
||||||
|
return &v
|
||||||
|
}
|
|
@ -0,0 +1,199 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/repository"
|
||||||
|
mgo "code.30cm.net/digimon/library-go/mongo"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/mon"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RoleRepositoryParam struct {
|
||||||
|
Conf *mgo.Conf
|
||||||
|
DBOpts []mon.Option
|
||||||
|
}
|
||||||
|
|
||||||
|
type RoleRepository struct {
|
||||||
|
DB mgo.DocumentDBUseCase
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRoleRepository(param RoleRepositoryParam) repository.RoleRepository {
|
||||||
|
e := entity.Role{}
|
||||||
|
db, err := mgo.NewDocumentDB(param.Conf, e.Collection(), param.DBOpts...)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &RoleRepository{
|
||||||
|
DB: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *RoleRepository) List(ctx context.Context, params repository.ListQuery) ([]*entity.Role, int64, error) {
|
||||||
|
// 構建查詢條件
|
||||||
|
filter := bson.M{}
|
||||||
|
|
||||||
|
if params.Name != nil {
|
||||||
|
filter["name"] = *params.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.ClientID != nil {
|
||||||
|
filter["client_id"] = *params.ClientID
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Status != nil {
|
||||||
|
filter["status"] = *params.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.UID != nil {
|
||||||
|
filter["uid"] = *params.UID
|
||||||
|
}
|
||||||
|
|
||||||
|
// 計算符合條件的總數
|
||||||
|
count, err := repo.DB.GetClient().CountDocuments(ctx, filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 構建查詢選項(分頁)
|
||||||
|
opts := options.Find().
|
||||||
|
SetSkip(params.PageSize * (params.PageIndex - 1)).
|
||||||
|
SetLimit(params.PageSize)
|
||||||
|
|
||||||
|
// 執行查詢
|
||||||
|
var result = make([]*entity.Role, 0, params.PageSize)
|
||||||
|
err = repo.DB.GetClient().Find(ctx, &result, filter, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *RoleRepository) GetByID(ctx context.Context, id string) (*entity.Role, error) {
|
||||||
|
oid, err := primitive.ObjectIDFromHex(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrInvalidObjectID
|
||||||
|
}
|
||||||
|
|
||||||
|
var result entity.Role
|
||||||
|
err = repo.DB.GetClient().FindOne(ctx, &result, bson.M{"_id": oid})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *RoleRepository) GetByUID(ctx context.Context, uid string) (*entity.Role, error) {
|
||||||
|
var result entity.Role
|
||||||
|
err := repo.DB.GetClient().FindOne(ctx, &result, bson.M{"uid": uid})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *RoleRepository) All(ctx context.Context, clientID *string) ([]*entity.Role, error) {
|
||||||
|
opt := options.Find().SetSort(bson.M{"_id": 1})
|
||||||
|
filter := bson.M{}
|
||||||
|
if clientID != nil {
|
||||||
|
filter["client_id"] = *clientID
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]*entity.Role, 0)
|
||||||
|
err := repo.DB.GetClient().Find(ctx, &result, filter, opt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create 建立新的 Role
|
||||||
|
func (repo *RoleRepository) Create(ctx context.Context, role *entity.Role) error {
|
||||||
|
if role == nil {
|
||||||
|
return fmt.Errorf("failed to get role")
|
||||||
|
}
|
||||||
|
|
||||||
|
if role.ID.IsZero() {
|
||||||
|
now := time.Now().UTC().UnixNano()
|
||||||
|
role.ID = primitive.NewObjectID()
|
||||||
|
role.CreateAt = now
|
||||||
|
role.UpdateAt = now
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := repo.DB.GetClient().InsertOne(ctx, role)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *RoleRepository) Update(ctx context.Context, role repository.UpdateReq) error {
|
||||||
|
now := time.Now().UTC().UnixNano()
|
||||||
|
// 動態構建更新內容
|
||||||
|
updateFields := bson.M{
|
||||||
|
"update_at": now, // 確保 `updateAt` 總是更新
|
||||||
|
}
|
||||||
|
|
||||||
|
if role.Name != nil {
|
||||||
|
updateFields["name"] = *role.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
if role.Status != nil {
|
||||||
|
updateFields["status"] = *role.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
if role.UID != nil {
|
||||||
|
updateFields["uid"] = *role.UID
|
||||||
|
}
|
||||||
|
|
||||||
|
if role.ClientID != nil {
|
||||||
|
updateFields["client_id"] = *role.ClientID
|
||||||
|
}
|
||||||
|
|
||||||
|
oid, err := primitive.ObjectIDFromHex(role.ID)
|
||||||
|
if err != nil {
|
||||||
|
return ErrInvalidObjectID
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = repo.DB.GetClient().UpdateOne(ctx, bson.M{"_id": oid}, bson.M{"$set": updateFields})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *RoleRepository) Delete(ctx context.Context, id string) error {
|
||||||
|
oid, err := primitive.ObjectIDFromHex(id)
|
||||||
|
if err != nil {
|
||||||
|
return ErrInvalidObjectID
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = repo.DB.GetClient().DeleteOne(ctx, bson.M{"_id": oid})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *RoleRepository) Index20250224UP(ctx context.Context) (*mongo.Cursor, error) {
|
||||||
|
repo.DB.PopulateMultiIndex(ctx, []string{
|
||||||
|
"name",
|
||||||
|
"client_id",
|
||||||
|
"status",
|
||||||
|
"uid",
|
||||||
|
}, []int32{1, 1, 1, 1}, false)
|
||||||
|
|
||||||
|
repo.DB.PopulateIndex(ctx, "uid", 1, false)
|
||||||
|
|
||||||
|
return repo.DB.GetClient().Indexes().List(ctx)
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/repository"
|
||||||
|
mgo "code.30cm.net/digimon/library-go/mongo"
|
||||||
|
"context"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/mon"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RolePermissionRepositoryParam struct {
|
||||||
|
Conf *mgo.Conf
|
||||||
|
DBOpts []mon.Option
|
||||||
|
}
|
||||||
|
|
||||||
|
type RolePermissionRepository struct {
|
||||||
|
DB mgo.DocumentDBUseCase
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRolePermissionRepository(param RoleRepositoryParam) repository.RolePermissionRepository {
|
||||||
|
e := entity.RolePermission{}
|
||||||
|
db, err := mgo.NewDocumentDB(param.Conf, e.Collection(), param.DBOpts...)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &RolePermissionRepository{
|
||||||
|
DB: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *RolePermissionRepository) Get(ctx context.Context, roleID string) ([]*entity.RolePermission, error) {
|
||||||
|
var result []*entity.RolePermission
|
||||||
|
err := repo.DB.GetClient().Find(ctx, &result, bson.M{"role_id": roleID})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *RolePermissionRepository) GetByPermissionID(ctx context.Context, permissionIDs []string) ([]*entity.RolePermission, error) {
|
||||||
|
var result []*entity.RolePermission // 修正 []*entity.RolePermission -> []entity.RolePermission
|
||||||
|
|
||||||
|
filter := bson.M{
|
||||||
|
"permission_id": bson.M{"$in": permissionIDs}, // 使用 $in 運算子來匹配多個 permission_id
|
||||||
|
}
|
||||||
|
|
||||||
|
err := repo.DB.GetClient().Find(ctx, &result, filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *RolePermissionRepository) Create(ctx context.Context, role entity.RolePermission) error {
|
||||||
|
if role.ID.IsZero() {
|
||||||
|
now := time.Now().UTC().UnixNano()
|
||||||
|
role.ID = primitive.NewObjectID()
|
||||||
|
role.CreateAt = now
|
||||||
|
role.UpdateAt = now
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := repo.DB.GetClient().InsertOne(ctx, role)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *RolePermissionRepository) Delete(ctx context.Context, roleID string, permission string) error {
|
||||||
|
filter := bson.M{
|
||||||
|
"role_id": roleID,
|
||||||
|
"permission_id": permission,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := repo.DB.GetClient().DeleteOne(ctx, filter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *RolePermissionRepository) Index20250225UP(ctx context.Context) (*mongo.Cursor, error) {
|
||||||
|
repo.DB.PopulateMultiIndex(ctx, []string{
|
||||||
|
"role_id",
|
||||||
|
"permission_id",
|
||||||
|
}, []int32{1, 1}, true)
|
||||||
|
|
||||||
|
repo.DB.PopulateIndex(ctx, "role_id", 1, false)
|
||||||
|
repo.DB.PopulateIndex(ctx, "permission_id", 1, false)
|
||||||
|
|
||||||
|
return repo.DB.GetClient().Indexes().List(ctx)
|
||||||
|
}
|
|
@ -0,0 +1,265 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/repository"
|
||||||
|
mgo "code.30cm.net/digimon/library-go/mongo"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetupTestRolePermissionRepository(db string) (repository.RolePermissionRepository, func(), error) {
|
||||||
|
h, p, tearDown, err := startMongoContainer()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conf := &mgo.Conf{
|
||||||
|
Schema: Schema,
|
||||||
|
Host: fmt.Sprintf("%s:%s", h, p),
|
||||||
|
Database: db,
|
||||||
|
MaxStaleness: 300,
|
||||||
|
MaxPoolSize: 100,
|
||||||
|
MinPoolSize: 100,
|
||||||
|
MaxConnIdleTime: 300,
|
||||||
|
Compressors: []string{},
|
||||||
|
EnableStandardReadWriteSplitMode: false,
|
||||||
|
ConnectTimeoutMs: 3000,
|
||||||
|
}
|
||||||
|
|
||||||
|
param := RoleRepositoryParam{
|
||||||
|
Conf: conf,
|
||||||
|
}
|
||||||
|
repo := NewRolePermissionRepository(param)
|
||||||
|
_, _ = repo.Index20250225UP(context.Background())
|
||||||
|
|
||||||
|
return repo, tearDown, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRolePermissionRepository_Create(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestRolePermissionRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
input entity.RolePermission
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "成功插入新的 RolePermission",
|
||||||
|
input: entity.RolePermission{
|
||||||
|
RoleID: "role_1",
|
||||||
|
PermissionID: "perm_1",
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "插入已經有 ID 的 RolePermission",
|
||||||
|
input: entity.RolePermission{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
RoleID: "role_2",
|
||||||
|
PermissionID: "perm_2",
|
||||||
|
CreateAt: time.Now().UnixNano(),
|
||||||
|
UpdateAt: time.Now().UnixNano(),
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := repo.Create(context.Background(), tc.input)
|
||||||
|
|
||||||
|
if tc.expectErr {
|
||||||
|
assert.Error(t, err, "應該返回錯誤")
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err, "不應該返回錯誤")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRolePermissionRepository_Delete(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestRolePermissionRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
// 先準備測試資料
|
||||||
|
existingRolePermission := entity.RolePermission{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
RoleID: "role_1",
|
||||||
|
PermissionID: "perm_1",
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.Create(context.Background(), existingRolePermission)
|
||||||
|
assert.NoError(t, err, "應該成功插入測試資料")
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
roleID string
|
||||||
|
permission string
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "成功刪除已存在的 RolePermission",
|
||||||
|
roleID: "role_1",
|
||||||
|
permission: "perm_1",
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "刪除不存在的 RolePermission,不應該報錯",
|
||||||
|
roleID: "role_2",
|
||||||
|
permission: "perm_2",
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := repo.Delete(context.Background(), tc.roleID, tc.permission)
|
||||||
|
|
||||||
|
if tc.expectErr {
|
||||||
|
assert.Error(t, err, "應該返回錯誤")
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err, "不應該返回錯誤")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRolePermissionRepository_GetByPermissionID(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestRolePermissionRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
// 先準備測試資料
|
||||||
|
existingRolePermissions := []entity.RolePermission{
|
||||||
|
{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
RoleID: "role_1",
|
||||||
|
PermissionID: "perm_1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
RoleID: "role_2",
|
||||||
|
PermissionID: "perm_2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
RoleID: "role_3",
|
||||||
|
PermissionID: "perm_3",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rp := range existingRolePermissions {
|
||||||
|
err := repo.Create(context.Background(), rp)
|
||||||
|
assert.NoError(t, err, "應該成功插入測試資料")
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
permissionIDs []string
|
||||||
|
expectedCount int
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "成功查詢符合的 RolePermission",
|
||||||
|
permissionIDs: []string{"perm_1", "perm_2"},
|
||||||
|
expectedCount: 2,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "查詢時沒有符合條件的 RolePermission",
|
||||||
|
permissionIDs: []string{"perm_99"},
|
||||||
|
expectedCount: 0,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "查詢時發生資料庫錯誤",
|
||||||
|
permissionIDs: nil, // 模擬無效的參數
|
||||||
|
expectedCount: 0,
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
results, err := repo.GetByPermissionID(context.Background(), tc.permissionIDs)
|
||||||
|
|
||||||
|
if tc.expectErr {
|
||||||
|
assert.Error(t, err, "應該返回錯誤")
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err, "不應該返回錯誤")
|
||||||
|
assert.Len(t, results, tc.expectedCount, "回傳結果數量應符合預期")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRolePermissionRepository_Get(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestRolePermissionRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
// 先準備測試資料
|
||||||
|
existingRolePermissions := []entity.RolePermission{
|
||||||
|
{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
RoleID: "role_1",
|
||||||
|
PermissionID: "perm_1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
RoleID: "role_1",
|
||||||
|
PermissionID: "perm_2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
RoleID: "role_2",
|
||||||
|
PermissionID: "perm_3",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rp := range existingRolePermissions {
|
||||||
|
err := repo.Create(context.Background(), rp)
|
||||||
|
assert.NoError(t, err, "應該成功插入測試資料")
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
roleID string
|
||||||
|
expectedCount int
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "成功查詢符合的 RolePermission",
|
||||||
|
roleID: "role_1",
|
||||||
|
expectedCount: 2,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "查詢時沒有符合條件的 RolePermission",
|
||||||
|
roleID: "role_99",
|
||||||
|
expectedCount: 0,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
results, err := repo.Get(context.Background(), tc.roleID)
|
||||||
|
|
||||||
|
if tc.expectErr {
|
||||||
|
assert.Error(t, err, "應該返回錯誤")
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err, "不應該返回錯誤")
|
||||||
|
assert.Len(t, results, tc.expectedCount, "回傳結果數量應符合預期")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,541 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/permission"
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/repository"
|
||||||
|
mgo "code.30cm.net/digimon/library-go/mongo"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetupTestRoleRepository(db string) (repository.RoleRepository, func(), error) {
|
||||||
|
h, p, tearDown, err := startMongoContainer()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conf := &mgo.Conf{
|
||||||
|
Schema: Schema,
|
||||||
|
Host: fmt.Sprintf("%s:%s", h, p),
|
||||||
|
Database: db,
|
||||||
|
MaxStaleness: 300,
|
||||||
|
MaxPoolSize: 100,
|
||||||
|
MinPoolSize: 100,
|
||||||
|
MaxConnIdleTime: 300,
|
||||||
|
Compressors: []string{},
|
||||||
|
EnableStandardReadWriteSplitMode: false,
|
||||||
|
ConnectTimeoutMs: 3000,
|
||||||
|
}
|
||||||
|
|
||||||
|
param := RoleRepositoryParam{
|
||||||
|
Conf: conf,
|
||||||
|
}
|
||||||
|
repo := NewRoleRepository(param)
|
||||||
|
_, _ = repo.Index20250224UP(context.Background())
|
||||||
|
|
||||||
|
return repo, tearDown, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRoleRepository_Create(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestRoleRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
input *entity.Role
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "成功建立新的角色",
|
||||||
|
input: &entity.Role{
|
||||||
|
Name: "Admin",
|
||||||
|
UID: "user123",
|
||||||
|
ClientID: "client456",
|
||||||
|
Status: 1,
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "自動生成 ID 及時間戳",
|
||||||
|
input: &entity.Role{
|
||||||
|
Name: "User",
|
||||||
|
UID: "user789",
|
||||||
|
ClientID: "client987",
|
||||||
|
Status: 1,
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "當角色為 nil 時應返回錯誤",
|
||||||
|
input: nil,
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := repo.Create(context.Background(), tc.input)
|
||||||
|
|
||||||
|
if tc.expectErr {
|
||||||
|
assert.Error(t, err, "應該返回錯誤")
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err, "不應該返回錯誤")
|
||||||
|
assert.NotEqual(t, primitive.NilObjectID, tc.input.ID, "應該自動生成 ObjectID")
|
||||||
|
assert.True(t, tc.input.CreateAt > 0, "應該自動設定 CreateAt")
|
||||||
|
assert.True(t, tc.input.UpdateAt > 0, "應該自動設定 UpdateAt")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRoleRepository_Update(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestRoleRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
// 預先建立一個角色
|
||||||
|
existingRole := &entity.Role{
|
||||||
|
Name: "Old Name",
|
||||||
|
UID: "old_uid",
|
||||||
|
ClientID: "old_client_id",
|
||||||
|
Status: 1,
|
||||||
|
}
|
||||||
|
err = repo.Create(context.Background(), existingRole)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
newName := "New Name"
|
||||||
|
newStatus := permission.Close
|
||||||
|
newUID := "new_uid"
|
||||||
|
newClientID := "new_client_id"
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
input repository.UpdateReq
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "成功更新角色",
|
||||||
|
input: repository.UpdateReq{
|
||||||
|
ID: existingRole.ID.Hex(),
|
||||||
|
Name: &newName,
|
||||||
|
Status: &newStatus,
|
||||||
|
UID: &newUID,
|
||||||
|
ClientID: &newClientID,
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "更新部分欄位",
|
||||||
|
input: repository.UpdateReq{
|
||||||
|
ID: existingRole.ID.Hex(),
|
||||||
|
Name: &newName,
|
||||||
|
Status: &newStatus,
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "無效的 ObjectID 應返回錯誤",
|
||||||
|
input: repository.UpdateReq{
|
||||||
|
ID: "invalid_object_id",
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "當 UpdateOne 失敗時應返回錯誤",
|
||||||
|
input: repository.UpdateReq{
|
||||||
|
ID: primitive.NewObjectID().Hex(), // 模擬一個不存在的 ID
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := repo.Update(context.Background(), tc.input)
|
||||||
|
|
||||||
|
if tc.expectErr {
|
||||||
|
assert.Error(t, err, "應該返回錯誤")
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err, "不應該返回錯誤")
|
||||||
|
|
||||||
|
// 驗證更新結果
|
||||||
|
updatedRole, err := repo.GetByID(context.Background(), existingRole.ID.Hex())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if tc.input.Name != nil {
|
||||||
|
assert.Equal(t, *tc.input.Name, updatedRole.Name, "名稱應該被更新")
|
||||||
|
}
|
||||||
|
if tc.input.Status != nil {
|
||||||
|
assert.Equal(t, *tc.input.Status, updatedRole.Status, "狀態應該被更新")
|
||||||
|
}
|
||||||
|
if tc.input.UID != nil {
|
||||||
|
assert.Equal(t, *tc.input.UID, updatedRole.UID, "UID 應該被更新")
|
||||||
|
}
|
||||||
|
if tc.input.ClientID != nil {
|
||||||
|
assert.Equal(t, *tc.input.ClientID, updatedRole.ClientID, "ClientID 應該被更新")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRoleRepository_Delete(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestRoleRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
// 預先建立一個角色
|
||||||
|
existingRole := &entity.Role{
|
||||||
|
Name: "Test Role",
|
||||||
|
UID: "test_uid",
|
||||||
|
ClientID: "test_client_id",
|
||||||
|
Status: 1,
|
||||||
|
}
|
||||||
|
err = repo.Create(context.Background(), existingRole)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
inputID string
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "成功刪除角色",
|
||||||
|
inputID: existingRole.ID.Hex(),
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "刪除不存在的角色不應報錯",
|
||||||
|
inputID: primitive.NewObjectID().Hex(),
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "無效的 ObjectID 應返回錯誤",
|
||||||
|
inputID: "invalid_object_id",
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := repo.Delete(context.Background(), tc.inputID)
|
||||||
|
|
||||||
|
if tc.expectErr {
|
||||||
|
assert.Error(t, err, "應該返回錯誤")
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err, "不應該返回錯誤")
|
||||||
|
|
||||||
|
// 驗證角色是否已刪除
|
||||||
|
if tc.inputID == existingRole.ID.Hex() {
|
||||||
|
_, err := repo.GetByID(context.Background(), existingRole.ID.Hex())
|
||||||
|
assert.Error(t, err, "應該找不到該角色")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRoleRepository_All(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestRoleRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
clientID1 := "client_1"
|
||||||
|
clientID2 := "client_2"
|
||||||
|
|
||||||
|
// 預先建立角色資料
|
||||||
|
roles := []*entity.Role{
|
||||||
|
{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Name: "Admin",
|
||||||
|
UID: "admin_uid",
|
||||||
|
ClientID: clientID1,
|
||||||
|
Status: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Name: "User",
|
||||||
|
UID: "user_uid",
|
||||||
|
ClientID: clientID1,
|
||||||
|
Status: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Name: "Manager",
|
||||||
|
UID: "manager_uid",
|
||||||
|
ClientID: clientID2,
|
||||||
|
Status: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 插入測試資料
|
||||||
|
for _, role := range roles {
|
||||||
|
err := repo.Create(context.Background(), role)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
clientID *string
|
||||||
|
expectLen int
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "查詢所有角色",
|
||||||
|
clientID: nil,
|
||||||
|
expectLen: len(roles),
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "根據 clientID 查詢角色",
|
||||||
|
clientID: &clientID1,
|
||||||
|
expectLen: 2,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "clientID 無匹配時應返回空",
|
||||||
|
clientID: new(string),
|
||||||
|
expectLen: 0,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result, err := repo.All(context.Background(), tc.clientID)
|
||||||
|
|
||||||
|
if tc.expectErr {
|
||||||
|
assert.Error(t, err, "應該返回錯誤")
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err, "不應該返回錯誤")
|
||||||
|
assert.Len(t, result, tc.expectLen, "返回的角色數量應符合預期")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRoleRepository_GetByUID(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestRoleRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
// 預先建立測試角色
|
||||||
|
existingRole := &entity.Role{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Name: "Admin",
|
||||||
|
UID: "admin_uid",
|
||||||
|
ClientID: "client_1",
|
||||||
|
Status: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.Create(context.Background(), existingRole)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
uid string
|
||||||
|
expectErr bool
|
||||||
|
expectNil bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "成功查詢角色",
|
||||||
|
uid: "admin_uid",
|
||||||
|
expectErr: false,
|
||||||
|
expectNil: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "查詢不存在的角色",
|
||||||
|
uid: "non_existent_uid",
|
||||||
|
expectErr: true,
|
||||||
|
expectNil: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result, err := repo.GetByUID(context.Background(), tc.uid)
|
||||||
|
|
||||||
|
if tc.expectErr {
|
||||||
|
assert.Error(t, err, "應該返回錯誤")
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err, "不應該返回錯誤")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectNil {
|
||||||
|
assert.Nil(t, result, "應該返回 nil")
|
||||||
|
} else {
|
||||||
|
assert.NotNil(t, result, "不應該返回 nil")
|
||||||
|
assert.Equal(t, existingRole.UID, result.UID, "UID 應相符")
|
||||||
|
assert.Equal(t, existingRole.Name, result.Name, "名稱應相符")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRoleRepository_GetByID(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestRoleRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
// 預先建立測試角色
|
||||||
|
existingRole := &entity.Role{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Name: "Admin",
|
||||||
|
UID: "admin_uid",
|
||||||
|
ClientID: "client_1",
|
||||||
|
Status: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.Create(context.Background(), existingRole)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
id string
|
||||||
|
expectErr bool
|
||||||
|
expectNil bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "成功查詢角色",
|
||||||
|
id: existingRole.ID.Hex(),
|
||||||
|
expectErr: false,
|
||||||
|
expectNil: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "查詢不存在的角色",
|
||||||
|
id: primitive.NewObjectID().Hex(),
|
||||||
|
expectErr: true,
|
||||||
|
expectNil: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "提供無效的 ObjectID",
|
||||||
|
id: "invalid_id",
|
||||||
|
expectErr: true,
|
||||||
|
expectNil: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result, err := repo.GetByID(context.Background(), tc.id)
|
||||||
|
|
||||||
|
if tc.expectErr {
|
||||||
|
assert.Error(t, err, "應該返回錯誤")
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err, "不應該返回錯誤")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectNil {
|
||||||
|
assert.Nil(t, result, "應該返回 nil")
|
||||||
|
} else {
|
||||||
|
assert.NotNil(t, result, "不應該返回 nil")
|
||||||
|
assert.Equal(t, existingRole.ID, result.ID, "ID 應相符")
|
||||||
|
assert.Equal(t, existingRole.Name, result.Name, "名稱應相符")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRoleRepository_List(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestRoleRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
// 預先建立測試角色
|
||||||
|
roles := []*entity.Role{
|
||||||
|
{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Name: "Admin",
|
||||||
|
UID: "admin_uid",
|
||||||
|
ClientID: "client_1",
|
||||||
|
Status: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Name: "User",
|
||||||
|
UID: "user_uid",
|
||||||
|
ClientID: "client_1",
|
||||||
|
Status: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Name: "Guest",
|
||||||
|
UID: "guest_uid",
|
||||||
|
ClientID: "client_2",
|
||||||
|
Status: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 插入測試資料
|
||||||
|
for _, role := range roles {
|
||||||
|
err := repo.Create(context.Background(), role)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
query repository.ListQuery
|
||||||
|
expectLen int
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "查詢所有角色",
|
||||||
|
query: repository.ListQuery{PageSize: 10, PageIndex: 1},
|
||||||
|
expectLen: 3,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "篩選名稱為 Admin",
|
||||||
|
query: repository.ListQuery{Name: ToPointer("Admin"), PageSize: 10, PageIndex: 1},
|
||||||
|
expectLen: 1,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "篩選特定 ClientID",
|
||||||
|
query: repository.ListQuery{ClientID: ToPointer("client_1"), PageSize: 10, PageIndex: 1},
|
||||||
|
expectLen: 2,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "篩選啟用 (Status=1) 的角色",
|
||||||
|
query: repository.ListQuery{Status: ToPointer(permission.Open), PageSize: 10, PageIndex: 1},
|
||||||
|
expectLen: 2,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "篩選特定 UID",
|
||||||
|
query: repository.ListQuery{UID: ToPointer("guest_uid"), PageSize: 10, PageIndex: 1},
|
||||||
|
expectLen: 1,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "測試分頁 PageSize=1, PageIndex=1",
|
||||||
|
query: repository.ListQuery{PageSize: 1, PageIndex: 1},
|
||||||
|
expectLen: 1,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "查詢無符合條件的角色",
|
||||||
|
query: repository.ListQuery{Name: ToPointer("NonExist"), PageSize: 10, PageIndex: 1},
|
||||||
|
expectLen: 0,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result, _, err := repo.List(context.Background(), tc.query)
|
||||||
|
|
||||||
|
if tc.expectErr {
|
||||||
|
assert.Error(t, err, "應該返回錯誤")
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err, "不應該返回錯誤")
|
||||||
|
assert.Len(t, result, tc.expectLen, "返回的角色數量應符合預期")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/testcontainers/testcontainers-go"
|
||||||
|
"github.com/testcontainers/testcontainers-go/wait"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Host = "127.0.0.1"
|
||||||
|
Port = "27017"
|
||||||
|
Schema = "mongodb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func startMongoContainer() (string, string, func(), error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
req := testcontainers.ContainerRequest{
|
||||||
|
Image: "mongo:latest",
|
||||||
|
ExposedPorts: []string{"27017/tcp"},
|
||||||
|
WaitingFor: wait.ForListeningPort("27017/tcp"),
|
||||||
|
}
|
||||||
|
|
||||||
|
mongoC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
|
||||||
|
ContainerRequest: req,
|
||||||
|
Started: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
port, err := mongoC.MappedPort(ctx, Port)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
host, err := mongoC.Host(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("mongodb://%s:%s", host, port.Port())
|
||||||
|
tearDown := func() {
|
||||||
|
mongoC.Terminate(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Connecting to %s\n", uri)
|
||||||
|
|
||||||
|
return host, port.Port(), tearDown, nil
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/repository"
|
||||||
|
mgo "code.30cm.net/digimon/library-go/mongo"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/mon"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserRoleRepositoryParam 定義 MongoDB 配置參數
|
||||||
|
type UserRoleRepositoryParam struct {
|
||||||
|
Conf *mgo.Conf
|
||||||
|
DBOpts []mon.Option
|
||||||
|
}
|
||||||
|
|
||||||
|
// userRoleRepository 實作 repository.UserRoleRepository 介面
|
||||||
|
type userRoleRepository struct {
|
||||||
|
DB mgo.DocumentDBUseCase
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUserRoleRepository 初始化 `UserRoleRepository`
|
||||||
|
func NewUserRoleRepository(param UserRoleRepositoryParam) repository.UserRoleRepository {
|
||||||
|
e := entity.UserRole{}
|
||||||
|
db, err := mgo.NewDocumentDB(param.Conf, e.Collection(), param.DBOpts...)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &userRoleRepository{
|
||||||
|
DB: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll 取得所有 UserRole
|
||||||
|
func (repo *userRoleRepository) GetAll(ctx context.Context) ([]*entity.UserRole, error) {
|
||||||
|
var result []*entity.UserRole
|
||||||
|
err := repo.DB.GetClient().Find(ctx, &result, bson.M{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByUserID 透過 UID 查詢 UserRole
|
||||||
|
func (repo *userRoleRepository) GetByUserID(ctx context.Context, uid string) (entity.UserRole, error) {
|
||||||
|
var result entity.UserRole
|
||||||
|
err := repo.DB.GetClient().FindOne(ctx, &result, bson.M{"uid": uid})
|
||||||
|
if err != nil {
|
||||||
|
return entity.UserRole{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUsersByRoleID 透過 RoleID 查詢所有使用此角色的使用者
|
||||||
|
func (repo *userRoleRepository) GetUsersByRoleID(ctx context.Context, roleID string) ([]entity.UserRole, error) {
|
||||||
|
var result []entity.UserRole
|
||||||
|
err := repo.DB.GetClient().Find(ctx, &result, bson.M{"role_id": roleID})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountUsersByRole 統計每個角色的使用者數量
|
||||||
|
func (repo *userRoleRepository) CountUsersByRole(ctx context.Context) ([]repository.RoleUserCount, error) {
|
||||||
|
pipeline := []bson.M{
|
||||||
|
{"$group": bson.M{
|
||||||
|
"_id": "$role_id",
|
||||||
|
"count": bson.M{"$sum": 1},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
var result []repository.RoleUserCount
|
||||||
|
err := repo.DB.GetClient().Aggregate(ctx, &result, pipeline)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUserRole 新增使用者角色
|
||||||
|
func (repo *userRoleRepository) CreateUserRole(ctx context.Context, param entity.UserRole) error {
|
||||||
|
if param.UID == "" {
|
||||||
|
return fmt.Errorf("uid can't be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if param.RoleID == "" {
|
||||||
|
return fmt.Errorf("role_id can't be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if param.ID.IsZero() {
|
||||||
|
now := time.Now().UTC().UnixNano()
|
||||||
|
param.ID = primitive.NewObjectID()
|
||||||
|
param.CreateAt = now
|
||||||
|
param.UpdateAt = now
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := repo.DB.GetClient().InsertOne(ctx, param)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserRole 更新使用者角色
|
||||||
|
func (repo *userRoleRepository) UpdateUserRole(ctx context.Context, uid, roleID string) (entity.UserRole, error) {
|
||||||
|
filter := bson.M{"uid": uid}
|
||||||
|
update := bson.M{
|
||||||
|
"$set": bson.M{
|
||||||
|
"role_id": roleID,
|
||||||
|
"update_at": time.Now().UTC().UnixNano(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var updated entity.UserRole
|
||||||
|
err := repo.DB.GetClient().FindOneAndUpdate(ctx, &updated, filter, update)
|
||||||
|
if err != nil {
|
||||||
|
return entity.UserRole{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (repo *userRoleRepository) Index20250225UP(ctx context.Context) (*mongo.Cursor, error) {
|
||||||
|
repo.DB.PopulateIndex(ctx, "role_id", 1, false)
|
||||||
|
repo.DB.PopulateIndex(ctx, "uid", 1, false)
|
||||||
|
|
||||||
|
return repo.DB.GetClient().Indexes().List(ctx)
|
||||||
|
}
|
|
@ -0,0 +1,336 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/repository"
|
||||||
|
mgo "code.30cm.net/digimon/library-go/mongo"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetupTestUserRoleRepository(db string) (repository.UserRoleRepository, func(), error) {
|
||||||
|
h, p, tearDown, err := startMongoContainer()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conf := &mgo.Conf{
|
||||||
|
Schema: Schema,
|
||||||
|
Host: fmt.Sprintf("%s:%s", h, p),
|
||||||
|
Database: db,
|
||||||
|
MaxStaleness: 300,
|
||||||
|
MaxPoolSize: 100,
|
||||||
|
MinPoolSize: 100,
|
||||||
|
MaxConnIdleTime: 300,
|
||||||
|
Compressors: []string{},
|
||||||
|
EnableStandardReadWriteSplitMode: false,
|
||||||
|
ConnectTimeoutMs: 3000,
|
||||||
|
}
|
||||||
|
|
||||||
|
param := UserRoleRepositoryParam{
|
||||||
|
Conf: conf,
|
||||||
|
}
|
||||||
|
repo := NewUserRoleRepository(param)
|
||||||
|
_, _ = repo.Index20250225UP(context.Background())
|
||||||
|
|
||||||
|
return repo, tearDown, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserRoleRepository_CreateUserRole(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestUserRoleRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
input entity.UserRole
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "成功新增使用者角色",
|
||||||
|
input: entity.UserRole{
|
||||||
|
UID: "user_123",
|
||||||
|
RoleID: "role_456",
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "異常:無效的輸入",
|
||||||
|
input: entity.UserRole{
|
||||||
|
RoleID: "role_456",
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := repo.CreateUserRole(context.Background(), tc.input)
|
||||||
|
if tc.expectErr {
|
||||||
|
assert.Error(t, err, "應該返回錯誤")
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err, "不應該返回錯誤")
|
||||||
|
|
||||||
|
// 檢查資料是否真的被插入
|
||||||
|
var inserted entity.UserRole
|
||||||
|
inserted, err = repo.GetByUserID(context.Background(), tc.input.UID)
|
||||||
|
fmt.Println(inserted)
|
||||||
|
assert.NoError(t, err, "應該能找到插入的資料")
|
||||||
|
assert.Equal(t, tc.input.UID, inserted.UID, "UID 應該匹配")
|
||||||
|
assert.Equal(t, tc.input.RoleID, inserted.RoleID, "RoleID 應該匹配")
|
||||||
|
assert.NotZero(t, inserted.CreateAt, "CreateAt 應該被設定")
|
||||||
|
assert.NotZero(t, inserted.UpdateAt, "UpdateAt 應該被設定")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserRoleRepository_UpdateUserRole(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestUserRoleRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
// 先插入測試數據
|
||||||
|
existingUserRole := entity.UserRole{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
UID: "user_123",
|
||||||
|
RoleID: "role_123",
|
||||||
|
CreateAt: time.Now().UTC().UnixNano(),
|
||||||
|
UpdateAt: time.Now().UTC().UnixNano(),
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.CreateUserRole(context.Background(), existingUserRole)
|
||||||
|
assert.NoError(t, err, "初始化數據應該成功")
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
uid string
|
||||||
|
newRoleID string
|
||||||
|
expectErr bool
|
||||||
|
expectedRole string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "成功更新使用者角色",
|
||||||
|
uid: "user_123",
|
||||||
|
newRoleID: "role_456",
|
||||||
|
expectErr: false,
|
||||||
|
expectedRole: "role_456",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "異常:更新的 UID 不存在",
|
||||||
|
uid: "non_existent_user",
|
||||||
|
newRoleID: "role_789",
|
||||||
|
expectErr: true,
|
||||||
|
expectedRole: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
_, err = repo.UpdateUserRole(context.Background(), tc.uid, tc.newRoleID)
|
||||||
|
|
||||||
|
if tc.expectErr {
|
||||||
|
assert.Error(t, err, "應該返回錯誤")
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err, "不應該返回錯誤")
|
||||||
|
// 檢查資料是否真的被更新
|
||||||
|
found, err := repo.GetByUserID(context.Background(), tc.uid)
|
||||||
|
assert.NoError(t, err, "應該能找到更新後的資料")
|
||||||
|
assert.Equal(t, tc.expectedRole, found.RoleID, "RoleID 應該匹配更新的值")
|
||||||
|
assert.NotZero(t, found.UpdateAt, "UpdateAt 應該被更新")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserRoleRepository_CountUsersByRole(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestUserRoleRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
// 先插入測試數據
|
||||||
|
testUsers := []entity.UserRole{
|
||||||
|
{ID: primitive.NewObjectID(), UID: "user_1", RoleID: "role_admin"},
|
||||||
|
{ID: primitive.NewObjectID(), UID: "user_2", RoleID: "role_admin"},
|
||||||
|
{ID: primitive.NewObjectID(), UID: "user_3", RoleID: "role_user"},
|
||||||
|
{ID: primitive.NewObjectID(), UID: "user_4", RoleID: "role_user"},
|
||||||
|
{ID: primitive.NewObjectID(), UID: "user_5", RoleID: "role_user"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, user := range testUsers {
|
||||||
|
err := repo.CreateUserRole(context.Background(), user)
|
||||||
|
assert.NoError(t, err, "應該成功插入測試數據")
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
expectedData []repository.RoleUserCount
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "成功統計角色使用者數量",
|
||||||
|
expectedData: []repository.RoleUserCount{
|
||||||
|
{RoleID: "role_admin", Count: 2},
|
||||||
|
{RoleID: "role_user", Count: 3},
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
//{
|
||||||
|
// name: "無資料時回傳空結果",
|
||||||
|
// expectedData: []repository.RoleUserCount{},
|
||||||
|
// expectErr: false,
|
||||||
|
//},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result, err := repo.CountUsersByRole(context.Background())
|
||||||
|
|
||||||
|
if tc.expectErr {
|
||||||
|
assert.Error(t, err, "應該返回錯誤")
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err, "不應該返回錯誤")
|
||||||
|
assert.ElementsMatch(t, tc.expectedData, result, "結果應該匹配預期的使用者數量統計")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestUserRoleRepository_GetUsersByRoleID 測試 GetUsersByRoleID 查詢指定 RoleID 的使用者
|
||||||
|
func TestUserRoleRepository_GetUsersByRoleID(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestUserRoleRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
// 插入測試數據
|
||||||
|
testUsers := []entity.UserRole{
|
||||||
|
{ID: primitive.NewObjectID(), UID: "user_1", RoleID: "role_admin"},
|
||||||
|
{ID: primitive.NewObjectID(), UID: "user_2", RoleID: "role_admin"},
|
||||||
|
{ID: primitive.NewObjectID(), UID: "user_3", RoleID: "role_user"},
|
||||||
|
{ID: primitive.NewObjectID(), UID: "user_4", RoleID: "role_user"},
|
||||||
|
{ID: primitive.NewObjectID(), UID: "user_5", RoleID: "role_user"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, user := range testUsers {
|
||||||
|
err := repo.CreateUserRole(context.Background(), user)
|
||||||
|
assert.NoError(t, err, "應該成功插入測試數據")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 測試案例
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
roleID string
|
||||||
|
expectedData []entity.UserRole
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "成功查詢 role_admin 的使用者",
|
||||||
|
roleID: "role_admin",
|
||||||
|
expectedData: []entity.UserRole{
|
||||||
|
{UID: "user_1", RoleID: "role_admin"},
|
||||||
|
{UID: "user_2", RoleID: "role_admin"},
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "成功查詢 role_user 的使用者",
|
||||||
|
roleID: "role_user",
|
||||||
|
expectedData: []entity.UserRole{
|
||||||
|
{UID: "user_3", RoleID: "role_user"},
|
||||||
|
{UID: "user_4", RoleID: "role_user"},
|
||||||
|
{UID: "user_5", RoleID: "role_user"},
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "查詢 role_guest,應回傳空陣列",
|
||||||
|
roleID: "role_guest",
|
||||||
|
expectedData: []entity.UserRole{},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result, err := repo.GetUsersByRoleID(context.Background(), tc.roleID)
|
||||||
|
|
||||||
|
if tc.expectErr {
|
||||||
|
assert.Error(t, err, "應該返回錯誤")
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err, "不應該返回錯誤")
|
||||||
|
fmt.Println(result)
|
||||||
|
compute := make([]entity.UserRole, 0, len(result))
|
||||||
|
res := make([]entity.UserRole, 0, len(result))
|
||||||
|
|
||||||
|
for _, item := range result {
|
||||||
|
compute = append(compute, entity.UserRole{
|
||||||
|
RoleID: item.RoleID,
|
||||||
|
UID: item.UID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range tc.expectedData {
|
||||||
|
res = append(res, entity.UserRole{
|
||||||
|
RoleID: item.RoleID,
|
||||||
|
UID: item.UID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
assert.ElementsMatch(t, compute, res, "結果應該匹配預期的使用者列表")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestUserRoleRepository_GetAll 測試 GetAll 取得所有使用者角色
|
||||||
|
func TestUserRoleRepository_GetAll(t *testing.T) {
|
||||||
|
repo, tearDown, err := SetupTestUserRoleRepository("testDB")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
// 插入測試數據
|
||||||
|
testUsers := []entity.UserRole{
|
||||||
|
{ID: primitive.NewObjectID(), UID: "user_1", RoleID: "role_admin"},
|
||||||
|
{ID: primitive.NewObjectID(), UID: "user_2", RoleID: "role_editor"},
|
||||||
|
{ID: primitive.NewObjectID(), UID: "user_3", RoleID: "role_viewer"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, user := range testUsers {
|
||||||
|
err := repo.CreateUserRole(context.Background(), user)
|
||||||
|
assert.NoError(t, err, "應該成功插入測試數據")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 測試案例
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
expectedData []*entity.UserRole
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "成功獲取所有使用者角色",
|
||||||
|
expectedData: []*entity.UserRole{
|
||||||
|
{UID: "user_1", RoleID: "role_admin"},
|
||||||
|
{UID: "user_2", RoleID: "role_editor"},
|
||||||
|
{UID: "user_3", RoleID: "role_viewer"},
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result, err := repo.GetAll(context.Background())
|
||||||
|
|
||||||
|
if tc.expectErr {
|
||||||
|
assert.Error(t, err, "應該返回錯誤")
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err, "不應該返回錯誤")
|
||||||
|
assert.Len(t, result, len(tc.expectedData), "結果應該包含正確數量的使用者角色")
|
||||||
|
//assert.ElementsMatch(t, tc.expectedData, result, "結果應該匹配預期的使用者角色")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,29 +1,37 @@
|
||||||
package usecase
|
package usecase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain"
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
||||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/permission"
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/permission"
|
||||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/repository"
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/repository"
|
||||||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/usecase"
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/usecase"
|
||||||
"code.30cm.net/digimon/library-go/errs"
|
"code.30cm.net/digimon/library-go/errs"
|
||||||
|
"code.30cm.net/digimon/library-go/errs/code"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/casbin/casbin/v2"
|
"github.com/casbin/casbin/v2"
|
||||||
"github.com/casbin/casbin/v2/model"
|
"github.com/casbin/casbin/v2/model"
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RBACUseCaseParam struct {
|
type RBACUseCaseParam struct {
|
||||||
ModulePath string
|
ModulePath string
|
||||||
permissionRepo repository.PermissionRepository
|
permissionRepo repository.PermissionRepository
|
||||||
|
roleRepo repository.RoleRepository
|
||||||
|
rolePermission repository.RolePermissionRepository
|
||||||
RBACRedisAdapter repository.RBACAdapter
|
RBACRedisAdapter repository.RBACAdapter
|
||||||
// role permission 之類的
|
// role permission 之類的
|
||||||
}
|
}
|
||||||
|
|
||||||
type RBACUseCase struct {
|
type RBACUseCase struct {
|
||||||
permissionRepo repository.PermissionRepository
|
permissionRepo repository.PermissionRepository
|
||||||
|
roleRepo repository.RoleRepository
|
||||||
adapter repository.RBACAdapter
|
adapter repository.RBACAdapter
|
||||||
|
rolePermission repository.RolePermissionRepository
|
||||||
instance *casbin.Enforcer
|
instance *casbin.Enforcer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +39,8 @@ func NewUseCase(param RBACUseCaseParam) usecase.RBACUseCase {
|
||||||
result := &RBACUseCase{
|
result := &RBACUseCase{
|
||||||
adapter: param.RBACRedisAdapter,
|
adapter: param.RBACRedisAdapter,
|
||||||
permissionRepo: param.permissionRepo,
|
permissionRepo: param.permissionRepo,
|
||||||
|
roleRepo: param.roleRepo,
|
||||||
|
rolePermission: param.rolePermission,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. 讀取 RBAC 模型 ->
|
// 1. 讀取 RBAC 模型 ->
|
||||||
|
@ -68,89 +78,107 @@ func (use *RBACUseCase) Check(ctx context.Context, role, path, method string) (u
|
||||||
Allow: ok,
|
Allow: ok,
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(p)
|
// 檢查是否有明碼查詢權限
|
||||||
//// 檢查是否有明碼查詢權限
|
if role == permission.AdminRoleUID {
|
||||||
//if role == domain.AdminRoleUID {
|
status.Select.PlainCode = true
|
||||||
// status.Select.PlainCode = true
|
} else if ok && method == http.MethodGet {
|
||||||
//} else if ok && method == http.MethodGet {
|
policy, err := use.instance.GetModel().HasPolicy("p", "p", []string{
|
||||||
// status.Select.PlainCode = use.rbac.GetModel().HasPolicy("p", "p", []string{
|
role, path, method, p[3] + ".plain_code",
|
||||||
// role, path, method, p[3] + ".plain_code",
|
})
|
||||||
// })
|
if err != nil {
|
||||||
//}
|
return usecase.CheckRolePermissionStatus{}, err
|
||||||
//
|
}
|
||||||
//limit := 4
|
status.Select.PlainCode = policy
|
||||||
//if len(p) >= limit {
|
|
||||||
// status.Select.PermissionName = p[3]
|
}
|
||||||
//}
|
|
||||||
|
limit := 4
|
||||||
|
if len(p) >= limit {
|
||||||
|
status.Select.PermissionName = p[3]
|
||||||
|
}
|
||||||
|
|
||||||
return status, nil
|
return status, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (use *RBACUseCase) LoadPolicy(ctx context.Context) error {
|
func (use *RBACUseCase) LoadPolicy(ctx context.Context) error {
|
||||||
|
// 取得所有permission -> permission tree 拿到所有節點,
|
||||||
status := permission.Open
|
permissions, err := use.permissionRepo.GetAll(ctx, nil)
|
||||||
// 取得所有permission -> permission tree 拿到有開啟的節點,如果付節點關閉,子節點也就不顯示了
|
|
||||||
permissions, err := use.permissionRepo.GetAll(ctx, &status)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("permissionRepo.AllStatus error: %w", err)
|
return fmt.Errorf("permissionRepo.AllStatus error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(permissions)
|
// 建立樹,我只要開啟的 Permission
|
||||||
|
tree := GeneratePermissionTree(permissions)
|
||||||
|
openMaps, err := tree.filterOpenNodes()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("GeneratePermissionTree.filterOpenNodes error: %w", err)
|
||||||
|
}
|
||||||
// 全部permission
|
// 全部permission
|
||||||
//permissionMap := make(map[int64]entity.Permission, len(permissions))
|
permissionMap := make(map[string]entity.Permission, len(permissions))
|
||||||
//for _, v := range permissions {
|
for k, v := range openMaps {
|
||||||
// permissionMap[v.ID] = v
|
permissionMap[k] = v
|
||||||
//}
|
}
|
||||||
|
|
||||||
// 全部角色
|
// 全部角色
|
||||||
//roles, err := r.roleRepo.All(ctx, 1)
|
roles, err := use.roleRepo.All(ctx, nil)
|
||||||
//if err != nil {
|
if err != nil {
|
||||||
// return fmt.Errorf("roleRepo.AllStatus error: %w", err)
|
return fmt.Errorf("roleRepo.All error: %w", err)
|
||||||
//}
|
}
|
||||||
//
|
|
||||||
//roleMap := make(map[int64]entity.Role, len(roles))
|
roleMap := make(map[string]entity.Role, len(roles))
|
||||||
//for _, v := range roles {
|
for _, v := range roles {
|
||||||
// roleMap[v.ID] = v
|
tmpValue := v
|
||||||
//}
|
roleMap[v.ID.Hex()] = *tmpValue
|
||||||
|
}
|
||||||
|
|
||||||
// 根據角色組合權限表
|
// 根據角色組合權限表
|
||||||
//for _, v := range roles {
|
for _, role := range roles {
|
||||||
// rolePermissions, err := r.rolePermissionRepo.Get(ctx, v.ID)
|
rolePermission, err := use.rolePermission.Get(ctx, role.ID.Hex())
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// return fmt.Errorf("rolePermissionRepo.Get ID: %d error: %w", v.ID, err)
|
e := errs.DatabaseErrorWithScopeL(
|
||||||
// }
|
code.CloudEPPermission,
|
||||||
//
|
domain.FailedToGetRolePermission,
|
||||||
// for _, rp := range rolePermissions {
|
logx.WithContext(ctx),
|
||||||
// role, ok := roleMap[rp.RoleID]
|
[]logx.LogField{
|
||||||
// if !ok {
|
{Key: "req", Value: fmt.Sprintf("role: %s", role.ID.Hex())},
|
||||||
// logrus.WithFields(logrus.Fields{
|
{Key: "func", Value: "RolePermissionRepo.Get"},
|
||||||
// "role_id": rp.RoleID,
|
{Key: "err", Value: err.Error()},
|
||||||
// }).Error("role not found")
|
},
|
||||||
//
|
"failed to get rolePermission")
|
||||||
// continue
|
|
||||||
// }
|
return e
|
||||||
//
|
}
|
||||||
// permission, ok := permissionMap[rp.PermissionID]
|
|
||||||
// if !ok {
|
for _, rp := range rolePermission {
|
||||||
// logrus.WithFields(logrus.Fields{
|
r, ok := roleMap[rp.RoleID]
|
||||||
// "permission_id": rp.PermissionID,
|
if !ok {
|
||||||
// }).Error("permission not found")
|
logx.Errorf(fmt.Sprintf("role_id: %s not found", rp.RoleID))
|
||||||
//
|
|
||||||
// continue
|
continue
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// if permission.HTTPPath == "" || permission.HTTPMethod == "" {
|
p, ok := permissionMap[rp.PermissionID]
|
||||||
// continue
|
if !ok {
|
||||||
// }
|
logx.Errorf(fmt.Sprintf("permission_id: %s not found", rp.PermissionID))
|
||||||
//
|
|
||||||
// // 根據策略model configs/rbac_model.conf填入policy_definition對應參數
|
continue
|
||||||
// err := persist.LoadPolicyArray([]string{"p", role.UID, permission.HTTPPath, permission.HTTPMethod, permission.Name}, model)
|
}
|
||||||
// if err != nil {
|
|
||||||
// return fmt.Errorf("persist.LoadPolicyArray error: %w", err)
|
if p.HTTPPath == "" || p.HTTPMethod == "" {
|
||||||
// }
|
continue
|
||||||
// }
|
}
|
||||||
//}
|
|
||||||
|
_, err = use.instance.AddPolicy(r.UID, p.HTTPPath, p.HTTPMethod, p.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = use.instance.LoadPolicy()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
package usecase
|
|
@ -0,0 +1,229 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/permission"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PermissionTree 用來管理權限節點,包含快速查詢 map 與輔助 map(例如名稱對應的 ID 列表)
|
||||||
|
type PermissionTree struct {
|
||||||
|
// dummy root 節點
|
||||||
|
root *PermissionNode
|
||||||
|
// permission id => node
|
||||||
|
nodes map[string]*PermissionNode
|
||||||
|
// permission name => permission id
|
||||||
|
names map[string][]string
|
||||||
|
mu sync.RWMutex // 保護樹的並發存取
|
||||||
|
}
|
||||||
|
|
||||||
|
type PermissionNode struct {
|
||||||
|
Data entity.Permission
|
||||||
|
Parent *PermissionNode
|
||||||
|
Children []*PermissionNode
|
||||||
|
}
|
||||||
|
|
||||||
|
// GeneratePermissionTree 根據扁平權限資料建立樹,並掛在 dummy root 下
|
||||||
|
func GeneratePermissionTree(permissions []entity.Permission) *PermissionTree {
|
||||||
|
tree := &PermissionTree{
|
||||||
|
nodes: make(map[string]*PermissionNode),
|
||||||
|
names: make(map[string][]string),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 建立所有節點
|
||||||
|
for _, perm := range permissions {
|
||||||
|
node := &PermissionNode{
|
||||||
|
Data: perm,
|
||||||
|
Children: []*PermissionNode{},
|
||||||
|
}
|
||||||
|
tree.nodes[perm.ID.Hex()] = node
|
||||||
|
tree.names[perm.Name] = append(tree.names[perm.Name], perm.ID.Hex())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 建立 dummy root 節點
|
||||||
|
tree.root = &PermissionNode{
|
||||||
|
Data: entity.Permission{ID: primitive.NewObjectID(), Name: "root"},
|
||||||
|
Children: []*PermissionNode{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 建立父子連結:若找不到父節點或 Parent 為 0,則掛在 dummy root 下
|
||||||
|
for _, node := range tree.nodes {
|
||||||
|
if node.Data.Parent == "" {
|
||||||
|
node.Parent = tree.root
|
||||||
|
tree.root.Children = append(tree.root.Children, node)
|
||||||
|
} else if parent, ok := tree.nodes[node.Data.Parent]; ok {
|
||||||
|
node.Parent = parent
|
||||||
|
parent.Children = append(parent.Children, node)
|
||||||
|
} else {
|
||||||
|
// 若父節點不存在,預設掛在 dummy root 下
|
||||||
|
node.Parent = tree.root
|
||||||
|
tree.root.Children = append(tree.root.Children, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tree
|
||||||
|
}
|
||||||
|
|
||||||
|
// getNode 輔助函數:根據 ID 從樹中查找節點
|
||||||
|
func (tree *PermissionTree) getNode(id string) *PermissionNode {
|
||||||
|
tree.mu.RLock()
|
||||||
|
defer tree.mu.RUnlock()
|
||||||
|
return tree.nodes[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tree *PermissionTree) put(node entity.Permission) {
|
||||||
|
parentNode := tree.getNode(node.Parent)
|
||||||
|
if parentNode == nil {
|
||||||
|
parentNode = tree.root
|
||||||
|
}
|
||||||
|
thisNode := &PermissionNode{
|
||||||
|
Data: node,
|
||||||
|
Parent: parentNode,
|
||||||
|
Children: make([]*PermissionNode, 0),
|
||||||
|
}
|
||||||
|
parentNode.Children = append(parentNode.Children, thisNode)
|
||||||
|
tree.names[node.Name] = append(tree.names[node.Name], node.ID.Hex())
|
||||||
|
tree.nodes[node.ID.Hex()] = thisNode
|
||||||
|
}
|
||||||
|
|
||||||
|
// filterOpenNodes 走訪整棵樹,列出有被打開的節點(父節點沒開,則底下的都不會開)
|
||||||
|
// 如果某個節點為非葉節點,則會檢查其子節點是否有啟用,否則該節點不會被展開。
|
||||||
|
// [permissionID] entity.Permission
|
||||||
|
func (tree *PermissionTree) filterOpenNodes() (map[string]entity.Permission, error) {
|
||||||
|
tree.mu.RLock()
|
||||||
|
defer tree.mu.RUnlock()
|
||||||
|
|
||||||
|
result := make(map[string]entity.Permission)
|
||||||
|
|
||||||
|
// dfs 為內部閉包,可存取 result
|
||||||
|
// 返回值 bool 表示目前節點或其子孫中是否存在有效 open 節點
|
||||||
|
var dfs func(node *PermissionNode) bool
|
||||||
|
dfs = func(node *PermissionNode) bool {
|
||||||
|
// 若本身狀態非 open,則整個分支不展開
|
||||||
|
if node.Data.Status != permission.Open {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 節點本身是 open,不論子節點狀態如何,先將該節點加入結果
|
||||||
|
result[node.Data.ID.Hex()] = node.Data
|
||||||
|
|
||||||
|
// 遞迴處理子節點
|
||||||
|
for _, child := range node.Children {
|
||||||
|
dfs(child)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 從 dummy root 的 Children 開始走訪(dummy root 本身不納入結果)
|
||||||
|
for _, child := range tree.root.Children {
|
||||||
|
dfs(child)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//// getFullParentPermissionIDs
|
||||||
|
//// 根據傳入的權限狀態 (Permissions) 回傳完整的權限 ID 列表,包含所有祖先。
|
||||||
|
//// 如果某個節點為非葉節點,則會檢查其子節點是否有啟用,否則該節點不會被展開。
|
||||||
|
//func (tree *PermissionTree) getFullParentPermissionIDs(permissions permission.Permissions) ([]string, error) {
|
||||||
|
// tree.mu.RLock()
|
||||||
|
// defer tree.mu.RUnlock()
|
||||||
|
//
|
||||||
|
// exist := make(map[string]bool)
|
||||||
|
// var ids []string
|
||||||
|
//
|
||||||
|
// for name, status := range permissions {
|
||||||
|
// if status != permission.OpenPermission {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// idList, ok := tree.nameToIDs[name]
|
||||||
|
// if !ok {
|
||||||
|
// return nil, NotFoundError
|
||||||
|
// }
|
||||||
|
// for _, pid := range idList {
|
||||||
|
// node, exists := tree.nodes[pid]
|
||||||
|
// if !exists || node == nil {
|
||||||
|
// return nil, NotFoundError
|
||||||
|
// }
|
||||||
|
// // 如果為父節點,檢查其子節點是否有啟用,若都關閉則不展開
|
||||||
|
// if len(node.Children) > 0 {
|
||||||
|
// var childOpen bool
|
||||||
|
// for _, child := range node.Children {
|
||||||
|
// if childStatus, ok := permissions[child.Data.Name]; ok && childStatus == permission.OpenPermission {
|
||||||
|
// childOpen = true
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if !childOpen {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// // 將該節點及所有祖先(直到 dummy root,不包括 dummy root)加入結果
|
||||||
|
// for cur := node; cur != nil && cur.Data.Name != "root"; cur = cur.Parent {
|
||||||
|
// if !exist[cur.Data.ID.Hex()] {
|
||||||
|
// ids = append(ids, cur.Data.ID.Hex())
|
||||||
|
// exist[cur.Data.ID.Hex()] = true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return ids, nil
|
||||||
|
//}
|
||||||
|
|
||||||
|
//// getFullParentPermissionStatus
|
||||||
|
//// 根據傳入的權限狀態 (Permissions) 回傳完整的權限狀態,包含所有祖先的名稱設為啟用。
|
||||||
|
//func (tree *PermissionTree) getFullParentPermissionStatus(permissions permission.Permissions) (permission.Permissions, error) {
|
||||||
|
// tree.mu.RLock()
|
||||||
|
// defer tree.mu.RUnlock()
|
||||||
|
//
|
||||||
|
// result := make(permission.Permissions)
|
||||||
|
// exist := make(map[string]bool)
|
||||||
|
//
|
||||||
|
// for name, status := range permissions {
|
||||||
|
// if status != permission.OpenPermission {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// idList, ok := tree.nameToIDs[name]
|
||||||
|
// if !ok {
|
||||||
|
// return nil, NotFoundError
|
||||||
|
// }
|
||||||
|
// for _, pid := range idList {
|
||||||
|
// node, exists := tree.nodes[pid]
|
||||||
|
// if !exists || node == nil {
|
||||||
|
// return nil, NotFoundError
|
||||||
|
// }
|
||||||
|
// // 將該節點及所有祖先標記為啟用
|
||||||
|
// for cur := node; cur != nil && cur.Data.Name != "root"; cur = cur.Parent {
|
||||||
|
// if !exist[cur.Data.ID.Hex()] {
|
||||||
|
// result[cur.Data.Name] = permission.OpenPermission
|
||||||
|
// exist[cur.Data.ID.Hex()] = true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return result, nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//// getFullParentPermission
|
||||||
|
//// 根據角色權限 (RolePermission) 列表,回傳完整的權限狀態(名稱->狀態),包含所有祖先
|
||||||
|
//func (tree *PermissionTree) getFullParentPermission(rolePermissions []entity.RolePermission) permission.Permissions {
|
||||||
|
// tree.mu.RLock()
|
||||||
|
// defer tree.mu.RUnlock()
|
||||||
|
//
|
||||||
|
// result := make(permission.Permissions)
|
||||||
|
// for _, rp := range rolePermissions {
|
||||||
|
// node, ok := tree.nodes[rp.PermissionID]
|
||||||
|
// if !ok || node == nil {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// // 將該節點及所有祖先設為啟用
|
||||||
|
// for cur := node; cur != nil && cur.Data.Name != "root"; cur = cur.Parent {
|
||||||
|
// result[cur.Data.Name] = permission.OpenPermission
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return result
|
||||||
|
//}
|
|
@ -0,0 +1,358 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
||||||
|
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/permission"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestGeneratePermissionTree 測試 GeneratePermissionTree 函數的建立樹功能
|
||||||
|
func TestGeneratePermissionTree(t *testing.T) {
|
||||||
|
// 準備測試資料
|
||||||
|
// p1 為根節點(Parent 為空)
|
||||||
|
p1ID := primitive.NewObjectID()
|
||||||
|
p1 := entity.Permission{
|
||||||
|
ID: p1ID,
|
||||||
|
Parent: "",
|
||||||
|
Name: "A",
|
||||||
|
}
|
||||||
|
|
||||||
|
// p2 的父節點為 p1
|
||||||
|
p2ID := primitive.NewObjectID()
|
||||||
|
p2 := entity.Permission{
|
||||||
|
ID: p2ID,
|
||||||
|
Parent: p1ID.Hex(),
|
||||||
|
Name: "B",
|
||||||
|
}
|
||||||
|
|
||||||
|
// p3 為另一個根節點(Parent 為空)
|
||||||
|
p3ID := primitive.NewObjectID()
|
||||||
|
p3 := entity.Permission{
|
||||||
|
ID: p3ID,
|
||||||
|
Parent: "",
|
||||||
|
Name: "C",
|
||||||
|
}
|
||||||
|
|
||||||
|
// p4 的 Parent 填寫一個不存在的 id,預期會掛在 dummy root 下
|
||||||
|
p4ID := primitive.NewObjectID()
|
||||||
|
p4 := entity.Permission{
|
||||||
|
ID: p4ID,
|
||||||
|
Parent: "nonexistent",
|
||||||
|
Name: "D",
|
||||||
|
}
|
||||||
|
|
||||||
|
permissions := []entity.Permission{p1, p2, p3, p4}
|
||||||
|
|
||||||
|
// 建立樹
|
||||||
|
tree := GeneratePermissionTree(permissions)
|
||||||
|
|
||||||
|
// 驗證 dummy root 下的子節點
|
||||||
|
// 預期 p1、p3、p4 均掛在 dummy root 下
|
||||||
|
if len(tree.root.Children) != 3 {
|
||||||
|
t.Errorf("expected 3 children under dummy root, got %d", len(tree.root.Children))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 驗證 p1 的節點是否正確
|
||||||
|
nodeP1, ok := tree.nodes[p1ID.Hex()]
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("node for permission A (p1) not found")
|
||||||
|
} else {
|
||||||
|
// p1 應該有一個子節點 p2
|
||||||
|
if len(nodeP1.Children) != 1 {
|
||||||
|
t.Errorf("expected node A to have 1 child, got %d", len(nodeP1.Children))
|
||||||
|
} else {
|
||||||
|
if nodeP1.Children[0].Data.ID != p2ID {
|
||||||
|
t.Errorf("expected node A child to be permission B (p2), got %v", nodeP1.Children[0].Data.ID.Hex())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 驗證 tree.names 的對應關係
|
||||||
|
checkNames := []struct {
|
||||||
|
name string
|
||||||
|
expectedID string
|
||||||
|
}{
|
||||||
|
{"A", p1ID.Hex()},
|
||||||
|
{"B", p2ID.Hex()},
|
||||||
|
{"C", p3ID.Hex()},
|
||||||
|
{"D", p4ID.Hex()},
|
||||||
|
}
|
||||||
|
for _, cn := range checkNames {
|
||||||
|
ids, ok := tree.names[cn.name]
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("name mapping for %s not found", cn.name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(ids) != 1 || ids[0] != cn.expectedID {
|
||||||
|
t.Errorf("expected name mapping for %s to be [%s], got %v", cn.name, cn.expectedID, ids)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetNode(t *testing.T) {
|
||||||
|
// 建立一個測試用的 PermissionTree
|
||||||
|
tree := &PermissionTree{
|
||||||
|
nodes: make(map[string]*PermissionNode),
|
||||||
|
names: make(map[string][]string),
|
||||||
|
}
|
||||||
|
id := primitive.NewObjectID()
|
||||||
|
// 建立一個測試節點,ID 為 "testID"
|
||||||
|
perm := entity.Permission{
|
||||||
|
ID: id,
|
||||||
|
Name: "Test Permission",
|
||||||
|
}
|
||||||
|
node := &PermissionNode{
|
||||||
|
Data: perm,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 將測試節點插入 tree 的 nodes map
|
||||||
|
tree.mu.Lock()
|
||||||
|
tree.nodes["testID"] = node
|
||||||
|
tree.mu.Unlock()
|
||||||
|
|
||||||
|
// 測試 getNode 返回存在的節點
|
||||||
|
got := tree.getNode("testID")
|
||||||
|
if got == nil {
|
||||||
|
t.Error("Expected to find node with id 'testID', but got nil")
|
||||||
|
} else if got.Data.ID.Hex() != id.Hex() {
|
||||||
|
t.Errorf("Expected node ID 'testID', got '%s'", got.Data.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 測試對不存在的 id,應回傳 nil
|
||||||
|
gotNil := tree.getNode("nonexistent")
|
||||||
|
if gotNil != nil {
|
||||||
|
t.Errorf("Expected nil for non-existent node, got %+v", gotNil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPut(t *testing.T) {
|
||||||
|
// 建立一個 PermissionTree,並初始化 dummy root
|
||||||
|
tree := &PermissionTree{
|
||||||
|
nodes: make(map[string]*PermissionNode),
|
||||||
|
names: make(map[string][]string),
|
||||||
|
}
|
||||||
|
// 建立 dummy root 節點,其 ID 為一個隨機 ObjectID,但不參與 mapping
|
||||||
|
dummyRootPerm := entity.Permission{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Parent: "",
|
||||||
|
Name: "root",
|
||||||
|
}
|
||||||
|
tree.root = &PermissionNode{
|
||||||
|
Data: dummyRootPerm,
|
||||||
|
Children: make([]*PermissionNode, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 測試 1:放入一筆 Parent 為空的節點,預期掛在 dummy root 下
|
||||||
|
permA := entity.Permission{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Parent: "", // 無父節點
|
||||||
|
Name: "A",
|
||||||
|
}
|
||||||
|
tree.put(permA)
|
||||||
|
nodeA := tree.getNode(permA.ID.Hex())
|
||||||
|
if nodeA == nil {
|
||||||
|
t.Errorf("Expected to find node A in tree.nodes")
|
||||||
|
}
|
||||||
|
if nodeA.Parent != tree.root {
|
||||||
|
t.Errorf("Expected node A's parent to be dummy root, got %v", nodeA.Parent.Data.Name)
|
||||||
|
}
|
||||||
|
if ids, ok := tree.names["A"]; !ok || len(ids) != 1 || ids[0] != permA.ID.Hex() {
|
||||||
|
t.Errorf("Expected tree.names for 'A' to contain %s, got %v", permA.ID.Hex(), tree.names["A"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 測試 2:放入一筆 Parent 為存在節點的節點
|
||||||
|
// 先放入父節點 permB
|
||||||
|
permB := entity.Permission{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Parent: "", // 掛在 dummy root 下
|
||||||
|
Name: "B",
|
||||||
|
}
|
||||||
|
tree.put(permB)
|
||||||
|
nodeB := tree.getNode(permB.ID.Hex())
|
||||||
|
if nodeB == nil {
|
||||||
|
t.Errorf("Expected to find node B in tree.nodes")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 再放入子節點 permC,其 Parent 為 permB.ID.Hex()
|
||||||
|
permC := entity.Permission{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Parent: permB.ID.Hex(),
|
||||||
|
Name: "C",
|
||||||
|
}
|
||||||
|
tree.put(permC)
|
||||||
|
nodeC := tree.getNode(permC.ID.Hex())
|
||||||
|
if nodeC == nil {
|
||||||
|
t.Errorf("Expected to find node C in tree.nodes")
|
||||||
|
}
|
||||||
|
if nodeC.Parent != nodeB {
|
||||||
|
t.Errorf("Expected node C's parent to be node B")
|
||||||
|
}
|
||||||
|
// 驗證 nodeB 的 Children 是否包含 nodeC
|
||||||
|
found := false
|
||||||
|
for _, child := range nodeB.Children {
|
||||||
|
if child.Data.ID == permC.ID {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("Expected node B's children to contain node C")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterOpenNodes(t *testing.T) {
|
||||||
|
// 建立一個 PermissionTree,初始化 nodes 與 names,並建立 dummy root 節點
|
||||||
|
tree := &PermissionTree{
|
||||||
|
nodes: make(map[string]*PermissionNode),
|
||||||
|
names: make(map[string][]string),
|
||||||
|
mu: sync.RWMutex{},
|
||||||
|
}
|
||||||
|
// 建立 dummy root 節點
|
||||||
|
dummyRootPerm := entity.Permission{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Parent: "",
|
||||||
|
Name: "root",
|
||||||
|
Status: permission.Open, // dummy root 狀態不影響結果
|
||||||
|
}
|
||||||
|
tree.root = &PermissionNode{
|
||||||
|
Data: dummyRootPerm,
|
||||||
|
Children: make([]*PermissionNode, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 建立測試節點
|
||||||
|
// Node A:Open, leaf
|
||||||
|
permA := entity.Permission{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Parent: "", // 無父節點 → 掛在 dummy root 下
|
||||||
|
Name: "A",
|
||||||
|
Status: permission.Open,
|
||||||
|
}
|
||||||
|
tree.put(permA)
|
||||||
|
|
||||||
|
// Node B:Open, non-leaf
|
||||||
|
permB := entity.Permission{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Parent: "", // 掛在 dummy root 下
|
||||||
|
Name: "B",
|
||||||
|
Status: permission.Open,
|
||||||
|
}
|
||||||
|
tree.put(permB)
|
||||||
|
|
||||||
|
// Node B1:Open, leaf, Parent = B
|
||||||
|
permB1 := entity.Permission{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Parent: permB.ID.Hex(),
|
||||||
|
Name: "B1",
|
||||||
|
Status: permission.Open,
|
||||||
|
}
|
||||||
|
tree.put(permB1)
|
||||||
|
|
||||||
|
// Node B2:Closed, leaf, Parent = B
|
||||||
|
permB2 := entity.Permission{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Parent: permB.ID.Hex(),
|
||||||
|
Name: "B2",
|
||||||
|
Status: permission.Close,
|
||||||
|
}
|
||||||
|
tree.put(permB2)
|
||||||
|
|
||||||
|
// Node C:Open, non-leaf,但其子節點皆 Closed → C 不會展開
|
||||||
|
permC := entity.Permission{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Parent: "", // 掛在 dummy root 下
|
||||||
|
Name: "C",
|
||||||
|
Status: permission.Close,
|
||||||
|
}
|
||||||
|
tree.put(permC)
|
||||||
|
|
||||||
|
// Node C1:Closed, leaf, Parent = C
|
||||||
|
permC1 := entity.Permission{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Parent: permC.ID.Hex(),
|
||||||
|
Name: "C1",
|
||||||
|
Status: permission.Open,
|
||||||
|
}
|
||||||
|
tree.put(permC1)
|
||||||
|
|
||||||
|
// Node C2:Closed, leaf, Parent = C
|
||||||
|
permC2 := entity.Permission{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Parent: permC.ID.Hex(),
|
||||||
|
Name: "C2",
|
||||||
|
Status: permission.Open,
|
||||||
|
}
|
||||||
|
tree.put(permC2)
|
||||||
|
|
||||||
|
// Node D:Closed, leaf, 無父節點
|
||||||
|
permD := entity.Permission{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Parent: "",
|
||||||
|
Name: "D",
|
||||||
|
Status: permission.Close,
|
||||||
|
}
|
||||||
|
tree.put(permD)
|
||||||
|
|
||||||
|
// Node E:Closed, leaf, 無父節點
|
||||||
|
permE := entity.Permission{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Parent: "",
|
||||||
|
Name: "E",
|
||||||
|
Status: permission.Open,
|
||||||
|
}
|
||||||
|
tree.put(permE)
|
||||||
|
|
||||||
|
// Node E1:Closed, leaf, Parent = E
|
||||||
|
permE1 := entity.Permission{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Parent: permE.ID.Hex(),
|
||||||
|
Name: "E1",
|
||||||
|
Status: permission.Close,
|
||||||
|
}
|
||||||
|
tree.put(permE1)
|
||||||
|
|
||||||
|
// Node E2:Closed, leaf, Parent = E
|
||||||
|
permE2 := entity.Permission{
|
||||||
|
ID: primitive.NewObjectID(),
|
||||||
|
Parent: permE.ID.Hex(),
|
||||||
|
Name: "E2",
|
||||||
|
Status: permission.Close,
|
||||||
|
}
|
||||||
|
tree.put(permE2)
|
||||||
|
|
||||||
|
// 執行 filterOpenNodes
|
||||||
|
openNodes, err := tree.filterOpenNodes()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("filterOpenNodes returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 預期結果:
|
||||||
|
// - Node A 應該包含(open 且為葉節點)
|
||||||
|
// - Node B 應該包含(open 且其子節點 B1 為 open)
|
||||||
|
// - Node B1 應該包含(open, leaf)
|
||||||
|
// - Node B2 不包含(closed)
|
||||||
|
// - Node C 不包含(closed)
|
||||||
|
// - Node C1, C2 不包含(C Node Close)
|
||||||
|
// - Node D 不包含(closed)
|
||||||
|
// - Node E 包含(open)
|
||||||
|
// - Node E1, E2 不包含(本身Close)
|
||||||
|
expectedIDs := map[string]bool{
|
||||||
|
permA.ID.Hex(): true,
|
||||||
|
permB.ID.Hex(): true,
|
||||||
|
permB1.ID.Hex(): true,
|
||||||
|
permE.ID.Hex(): true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 檢查結果是否只包含預期的節點
|
||||||
|
for id, perm := range openNodes {
|
||||||
|
if !expectedIDs[id] {
|
||||||
|
t.Errorf("Unexpected node in openNodes: id=%s, name=%s", id, perm.Name)
|
||||||
|
}
|
||||||
|
delete(expectedIDs, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(expectedIDs) != 0 {
|
||||||
|
t.Errorf("Expected nodes not found in openNodes: %v", expectedIDs)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue