feat: add repository permission role role_permission user_role table
This commit is contained in:
parent
547b3b06dd
commit
cccad97512
7
Makefile
7
Makefile
|
@ -49,12 +49,13 @@ build-docker:
|
|||
.PHONY: mock-gen
|
||||
mock-gen: # 建立 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"
|
||||
|
||||
.PHONY: migrate-database
|
||||
migrate-database:
|
||||
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/segmentio/ksuid v1.0.4
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/testcontainers/testcontainers-go v0.34.0
|
||||
github.com/zeromicro/go-zero v1.8.0
|
||||
go.mongodb.org/mongo-driver v1.17.2
|
||||
go.uber.org/mock v0.5.0
|
||||
|
@ -18,20 +19,33 @@ 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/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect
|
||||
github.com/casbin/govaluate v1.3.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.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-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/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/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/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/jsonreference v0.20.2 // 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/json-iterator/go v1.1.12 // 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/mattn/go-colorable v0.1.13 // 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/reflect2 v1.0.2 // 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/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/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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/prometheus/client_golang v1.20.5 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.55.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // 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/sirupsen/logrus v1.9.3 // 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/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // 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/client/pkg/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/contrib/instrumentation/net/http/otelhttp v0.49.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/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/mongo v0.0.9 h1:fPciIE5B85tXpLg8aeVQqKVbLnfpVAk9xbMu7pE2tVw=
|
||||
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/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||
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/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
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/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/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.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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
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/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/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
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/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.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/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
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-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/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
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/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/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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
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/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
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-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
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/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
|
||||
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/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
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/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
||||
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/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c=
|
||||
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
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/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
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/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
|
||||
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/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/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
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/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
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.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
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/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
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/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||
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/go.mod h1:xDBF+/iDzj30zPvu6HNUIbpz1J6+/g3Sx9D/DytJfss=
|
||||
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.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/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/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
||||
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/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-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-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-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-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-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-20220811171246-fbc7d0a398ab/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/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
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/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80=
|
||||
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"
|
||||
)
|
||||
|
||||
//
|
||||
//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 {
|
||||
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: 停用
|
||||
CreateAt int64 `bson:"create_at"`
|
||||
UpdateAt int64 `bson:"update_at"`
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
|
||||
const (
|
||||
TokenServerErrorCode = 1 + iota
|
||||
TokenServerRedisErrorCode
|
||||
TokenValidateErrorCode
|
||||
TokenClaimErrorCode
|
||||
TokenCreateErrorCode
|
||||
|
@ -19,17 +18,8 @@ const (
|
|||
TokenCancelErrorCode
|
||||
TokensCancelErrorCode
|
||||
TokenGetErrorCode
|
||||
NewOneTokenErrorCode
|
||||
DelOneTokenErrorCode
|
||||
SendTooShortErrorCode
|
||||
SetForgetPasswordRedisErrorCode
|
||||
FailedToGetCorrectVerifyCode
|
||||
SendVerifyCodeRedisErrorCode
|
||||
GenerateVerifyCodeRedisErrorCode
|
||||
FailedToCheckVerifyCode
|
||||
AccountPlatformNotCorrectErrorCode
|
||||
|
||||
PermissionDeleteErrorCode
|
||||
FailedToGetRolePermission
|
||||
)
|
||||
|
||||
func TokenError(ec ers.ErrorCode, s ...string) *ers.LibError {
|
||||
|
|
|
@ -8,20 +8,23 @@ const (
|
|||
)
|
||||
|
||||
const (
|
||||
ClosePermission string = "close"
|
||||
OpenPermission string = "open"
|
||||
ClosePermission StatusCode = "close"
|
||||
OpenPermission StatusCode = "open"
|
||||
)
|
||||
|
||||
func (s Status) String() string {
|
||||
status, ok := statusMap[s]
|
||||
if ok {
|
||||
return status
|
||||
return string(status)
|
||||
}
|
||||
|
||||
return ClosePermission
|
||||
return string(ClosePermission)
|
||||
}
|
||||
|
||||
var statusMap = map[Status]string{
|
||||
var statusMap = map[Status]StatusCode{
|
||||
Open: OpenPermission,
|
||||
Close: ClosePermission,
|
||||
}
|
||||
|
||||
type StatusCode string
|
||||
type Permissions map[string]StatusCode
|
||||
|
|
|
@ -6,3 +6,5 @@ const (
|
|||
BackendUser Type = iota + 1
|
||||
FrontendUser
|
||||
)
|
||||
|
||||
const AdminRoleUID = "GodDog"
|
||||
|
|
|
@ -24,7 +24,7 @@ func (rule Rule) ToString() []string {
|
|||
rule.Field2, rule.Field3, rule.Field4, rule.Field5,
|
||||
}
|
||||
// 移除空字串,提高效能
|
||||
var result []string
|
||||
result := make([]string, 0, len(fields))
|
||||
for _, field := range fields {
|
||||
if 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 {
|
||||
// GetAll 取得所有權限列表
|
||||
GetAll(ctx context.Context, status *permission.Status) ([]entity.Permission, error)
|
||||
// GetAllByID 以權限 ID 作為鍵取得所有權限的映射
|
||||
GetAllByID(ctx context.Context, status *permission.Status) (map[string]entity.Permission, error)
|
||||
// GetAllIntoIDMap 以權限 ID 作為鍵取得所有權限的映射
|
||||
GetAllIntoIDMap(ctx context.Context, status *permission.Status) (map[string]entity.Permission, error)
|
||||
// FindOne 根據查詢條件取得單筆權限資料
|
||||
FindOne(ctx context.Context, query PermissionQuery) (entity.Permission, error)
|
||||
// FindByNames 根據權限名稱列表查詢權限資料
|
||||
|
|
|
@ -2,41 +2,39 @@ 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"
|
||||
"context"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
)
|
||||
|
||||
type RoleRepository interface {
|
||||
Insert(ctx context.Context, role *entity.Role) error
|
||||
Update(ctx context.Context, role *entity.Role) error
|
||||
Delete(ctx context.Context, role *entity.Role) error
|
||||
List()
|
||||
ListAll()
|
||||
Get()
|
||||
GetByUID()
|
||||
List(ctx context.Context, param ListQuery) ([]*entity.Role, int64, error)
|
||||
GetByID(ctx context.Context, id string) (*entity.Role, error)
|
||||
GetByUID(ctx context.Context, uid string) (*entity.Role, error)
|
||||
All(ctx context.Context, clientID *string) ([]*entity.Role, error)
|
||||
Create(ctx context.Context, role *entity.Role) error
|
||||
Update(ctx context.Context, data UpdateReq) error
|
||||
Delete(ctx context.Context, id string) error
|
||||
RoleIndex
|
||||
}
|
||||
|
||||
type RoleRepository interface {
|
||||
Page(ctx context.Context, filter PageRoleFilter, page, size int) ([]entity.Role, int64, 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 RoleIndex interface {
|
||||
Index20250224UP(ctx context.Context) (*mongo.Cursor, error)
|
||||
}
|
||||
|
||||
type PageRoleFilter struct {
|
||||
ClientID int
|
||||
UID string
|
||||
Name string
|
||||
Permissions []string
|
||||
Status int
|
||||
type ListQuery struct {
|
||||
PageSize int64 // 必填
|
||||
PageIndex int64 // 必填
|
||||
ClientID *string
|
||||
UID *string
|
||||
Name *string
|
||||
Status *permission.Status
|
||||
}
|
||||
|
||||
type RolePermissionRepository interface {
|
||||
BindRolePermission()
|
||||
UnBindRolePermission()
|
||||
GetRolePermission()
|
||||
GetByPermissionID()
|
||||
type UpdateReq struct {
|
||||
ID string
|
||||
Name *string
|
||||
UID *string
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -153,7 +153,7 @@ func (repo *PermissionRepository) FindByNames(ctx context.Context, names []strin
|
|||
result := make([]entity.Permission, 0)
|
||||
// 使用 $in 操作符查詢 name 在 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 {
|
||||
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
|
||||
|
||||
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/repository"
|
||||
"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"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/casbin/casbin/v2"
|
||||
"github.com/casbin/casbin/v2/model"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RBACUseCaseParam struct {
|
||||
ModulePath string
|
||||
permissionRepo repository.PermissionRepository
|
||||
roleRepo repository.RoleRepository
|
||||
rolePermission repository.RolePermissionRepository
|
||||
RBACRedisAdapter repository.RBACAdapter
|
||||
// role permission 之類的
|
||||
}
|
||||
|
||||
type RBACUseCase struct {
|
||||
permissionRepo repository.PermissionRepository
|
||||
roleRepo repository.RoleRepository
|
||||
adapter repository.RBACAdapter
|
||||
rolePermission repository.RolePermissionRepository
|
||||
instance *casbin.Enforcer
|
||||
}
|
||||
|
||||
|
@ -31,6 +39,8 @@ func NewUseCase(param RBACUseCaseParam) usecase.RBACUseCase {
|
|||
result := &RBACUseCase{
|
||||
adapter: param.RBACRedisAdapter,
|
||||
permissionRepo: param.permissionRepo,
|
||||
roleRepo: param.roleRepo,
|
||||
rolePermission: param.rolePermission,
|
||||
}
|
||||
|
||||
// 1. 讀取 RBAC 模型 ->
|
||||
|
@ -68,89 +78,107 @@ func (use *RBACUseCase) Check(ctx context.Context, role, path, method string) (u
|
|||
Allow: ok,
|
||||
}
|
||||
|
||||
fmt.Println(p)
|
||||
//// 檢查是否有明碼查詢權限
|
||||
//if role == domain.AdminRoleUID {
|
||||
// status.Select.PlainCode = true
|
||||
//} else if ok && method == http.MethodGet {
|
||||
// status.Select.PlainCode = use.rbac.GetModel().HasPolicy("p", "p", []string{
|
||||
// role, path, method, p[3] + ".plain_code",
|
||||
// })
|
||||
//}
|
||||
//
|
||||
//limit := 4
|
||||
//if len(p) >= limit {
|
||||
// status.Select.PermissionName = p[3]
|
||||
//}
|
||||
// 檢查是否有明碼查詢權限
|
||||
if role == permission.AdminRoleUID {
|
||||
status.Select.PlainCode = true
|
||||
} else if ok && method == http.MethodGet {
|
||||
policy, err := use.instance.GetModel().HasPolicy("p", "p", []string{
|
||||
role, path, method, p[3] + ".plain_code",
|
||||
})
|
||||
if err != nil {
|
||||
return usecase.CheckRolePermissionStatus{}, err
|
||||
}
|
||||
status.Select.PlainCode = policy
|
||||
|
||||
}
|
||||
|
||||
limit := 4
|
||||
if len(p) >= limit {
|
||||
status.Select.PermissionName = p[3]
|
||||
}
|
||||
|
||||
return status, nil
|
||||
}
|
||||
|
||||
func (use *RBACUseCase) LoadPolicy(ctx context.Context) error {
|
||||
|
||||
status := permission.Open
|
||||
// 取得所有permission -> permission tree 拿到有開啟的節點,如果付節點關閉,子節點也就不顯示了
|
||||
permissions, err := use.permissionRepo.GetAll(ctx, &status)
|
||||
// 取得所有permission -> permission tree 拿到所有節點,
|
||||
permissions, err := use.permissionRepo.GetAll(ctx, nil)
|
||||
if err != nil {
|
||||
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
|
||||
//permissionMap := make(map[int64]entity.Permission, len(permissions))
|
||||
//for _, v := range permissions {
|
||||
// permissionMap[v.ID] = v
|
||||
//}
|
||||
permissionMap := make(map[string]entity.Permission, len(permissions))
|
||||
for k, v := range openMaps {
|
||||
permissionMap[k] = v
|
||||
}
|
||||
|
||||
// 全部角色
|
||||
//roles, err := r.roleRepo.All(ctx, 1)
|
||||
//if err != nil {
|
||||
// return fmt.Errorf("roleRepo.AllStatus error: %w", err)
|
||||
//}
|
||||
//
|
||||
//roleMap := make(map[int64]entity.Role, len(roles))
|
||||
//for _, v := range roles {
|
||||
// roleMap[v.ID] = v
|
||||
//}
|
||||
roles, err := use.roleRepo.All(ctx, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("roleRepo.All error: %w", err)
|
||||
}
|
||||
|
||||
roleMap := make(map[string]entity.Role, len(roles))
|
||||
for _, v := range roles {
|
||||
tmpValue := v
|
||||
roleMap[v.ID.Hex()] = *tmpValue
|
||||
}
|
||||
|
||||
// 根據角色組合權限表
|
||||
//for _, v := range roles {
|
||||
// rolePermissions, err := r.rolePermissionRepo.Get(ctx, v.ID)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("rolePermissionRepo.Get ID: %d error: %w", v.ID, err)
|
||||
// }
|
||||
//
|
||||
// for _, rp := range rolePermissions {
|
||||
// role, ok := roleMap[rp.RoleID]
|
||||
// if !ok {
|
||||
// logrus.WithFields(logrus.Fields{
|
||||
// "role_id": rp.RoleID,
|
||||
// }).Error("role not found")
|
||||
//
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// permission, ok := permissionMap[rp.PermissionID]
|
||||
// if !ok {
|
||||
// logrus.WithFields(logrus.Fields{
|
||||
// "permission_id": rp.PermissionID,
|
||||
// }).Error("permission not found")
|
||||
//
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// if permission.HTTPPath == "" || permission.HTTPMethod == "" {
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// // 根據策略model configs/rbac_model.conf填入policy_definition對應參數
|
||||
// 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)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
for _, role := range roles {
|
||||
rolePermission, err := use.rolePermission.Get(ctx, role.ID.Hex())
|
||||
if err != nil {
|
||||
e := errs.DatabaseErrorWithScopeL(
|
||||
code.CloudEPPermission,
|
||||
domain.FailedToGetRolePermission,
|
||||
logx.WithContext(ctx),
|
||||
[]logx.LogField{
|
||||
{Key: "req", Value: fmt.Sprintf("role: %s", role.ID.Hex())},
|
||||
{Key: "func", Value: "RolePermissionRepo.Get"},
|
||||
{Key: "err", Value: err.Error()},
|
||||
},
|
||||
"failed to get rolePermission")
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
for _, rp := range rolePermission {
|
||||
r, ok := roleMap[rp.RoleID]
|
||||
if !ok {
|
||||
logx.Errorf(fmt.Sprintf("role_id: %s not found", rp.RoleID))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
p, ok := permissionMap[rp.PermissionID]
|
||||
if !ok {
|
||||
logx.Errorf(fmt.Sprintf("permission_id: %s not found", rp.PermissionID))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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