diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3ff5645b..f4b5ab51 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -54,22 +54,22 @@ jobs: git diff --compact-summary --exit-code || \ (echo; echo "Unexpected difference in directories after code generation. Run 'go generate ./...' command and commit."; exit 1) - # Run acceptance tests in a matrix with Terraform CLI versions test: - name: Terraform Provider Acceptance Tests + name: Tests needs: build runs-on: ubuntu-latest timeout-minutes: 15 - strategy: - fail-fast: false - matrix: - # list whatever Terraform versions here you would like to support - terraform: - - "1.0.*" - - "1.1.*" - - "1.2.*" - - "1.3.*" - - "1.4.*" + # enable strategy when run acceptance tests + # strategy: + # fail-fast: false + # matrix: + # # list whatever Terraform versions here you would like to support + # terraform: + # - "1.0.*" + # - "1.1.*" + # - "1.2.*" + # - "1.3.*" + # - "1.4.*" steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 @@ -77,11 +77,9 @@ jobs: go-version-file: "go.mod" cache: true - uses: hashicorp/setup-terraform@651471c36a6092792c552e8b1bef71e592b462d8 # v3.1.1 - with: - terraform_version: ${{ matrix.terraform }} - terraform_wrapper: false + # with: + # terraform_version: ${{ matrix.terraform }} + # terraform_wrapper: false - run: go mod download - - env: - TF_ACC: "1" - run: go test -v -cover ./internal/* + - run: go test -v -cover ./internal/* timeout-minutes: 10 diff --git a/docs/resources/user.md b/docs/resources/user.md new file mode 100644 index 00000000..2b69e129 --- /dev/null +++ b/docs/resources/user.md @@ -0,0 +1,49 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "trocco_user Resource - trocco" +subcategory: "" +description: |- + Provides a TROCCO user resource. +--- + +# trocco_user (Resource) + +Provides a TROCCO user resource. + +## Example Usage + +```terraform +resource "trocco_user" "example" { + email = "trocco@example.com" + password = "Jb1p4f1uuC" + role = "member" + can_use_audit_log = false + is_restricted_connection_modify = false +} +``` + + +## Schema + +### Required + +- `email` (String) The email of the user. +- `role` (String) The role of the user. Valid value is `super_admin`, `admin`, or `member`. + +### Optional + +- `can_use_audit_log` (Boolean) Whether the user can use the audit log. +- `is_restricted_connection_modify` (Boolean) Whether the user is restricted to modify connections. +- `password` (String, Sensitive) The password of the user. It must be at least 8 characters long and contain at least one letter and one number. It is required when creating a new user but optional during updates. + +### Read-Only + +- `id` (Number) The ID of the user. + +## Import + +Import is supported using the following syntax: + +```shell +terraform import trocco_user.example +``` diff --git a/examples/resources/trocco_user/import.sh b/examples/resources/trocco_user/import.sh new file mode 100644 index 00000000..54431991 --- /dev/null +++ b/examples/resources/trocco_user/import.sh @@ -0,0 +1 @@ +terraform import trocco_user.example diff --git a/examples/resources/trocco_user/resource.tf b/examples/resources/trocco_user/resource.tf new file mode 100644 index 00000000..0bc23c8e --- /dev/null +++ b/examples/resources/trocco_user/resource.tf @@ -0,0 +1,7 @@ +resource "trocco_user" "example" { + email = "trocco@example.com" + password = "Jb1p4f1uuC" + role = "member" + can_use_audit_log = false + is_restricted_connection_modify = false +} diff --git a/go.mod b/go.mod index 5f00b7a0..e39a2a26 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,23 @@ require ( github.com/hashicorp/terraform-plugin-go v0.23.0 ) +require ( + github.com/agext/levenshtein v1.2.2 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect + github.com/hashicorp/hcl/v2 v2.21.0 // indirect + github.com/hashicorp/logutils v1.0.0 // indirect + github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/samber/lo v1.47.0 // indirect + github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + google.golang.org/appengine v1.6.8 // indirect +) + require ( github.com/BurntSushi/toml v1.2.1 // indirect github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect @@ -33,10 +50,11 @@ require ( github.com/hashicorp/go-plugin v1.6.0 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-version v1.7.0 // indirect - github.com/hashicorp/hc-install v0.7.0 // indirect + github.com/hashicorp/hc-install v0.8.0 // indirect github.com/hashicorp/terraform-exec v0.21.0 // indirect github.com/hashicorp/terraform-json v0.22.1 // indirect github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect + github.com/hashicorp/terraform-plugin-testing v1.10.0 github.com/hashicorp/terraform-registry-address v0.2.3 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect @@ -56,14 +74,14 @@ require ( github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/yuin/goldmark v1.7.1 // indirect github.com/yuin/goldmark-meta v1.1.0 // indirect - github.com/zclconf/go-cty v1.14.4 // indirect + github.com/zclconf/go-cty v1.15.0 // indirect go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 // indirect - golang.org/x/mod v0.17.0 // indirect + golang.org/x/mod v0.19.0 // indirect golang.org/x/net v0.25.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/sys v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/grpc v1.63.2 // indirect google.golang.org/protobuf v1.34.0 // indirect diff --git a/go.sum b/go.sum index 3d64f204..7077a8f1 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,9 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg= github.com/ProtonMail/go-crypto v1.1.0-alpha.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= +github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= @@ -46,8 +49,13 @@ github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZt github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -63,6 +71,8 @@ github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuD github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI= +github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= @@ -70,6 +80,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A= github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -77,6 +89,12 @@ github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKe github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/hc-install v0.7.0 h1:Uu9edVqjKQxxuD28mR5TikkKDd/p55S8vzPC1659aBk= github.com/hashicorp/hc-install v0.7.0/go.mod h1:ELmmzZlGnEcqoUMKUuykHaPCIR1sYLYX+KSggWSKZuA= +github.com/hashicorp/hc-install v0.8.0 h1:LdpZeXkZYMQhoKPCecJHlKvUkQFixN/nvyR1CdfOLjI= +github.com/hashicorp/hc-install v0.8.0/go.mod h1:+MwJYjDfCruSD/udvBmRB22Nlkwwkwf5sAB6uTIhSaU= +github.com/hashicorp/hcl/v2 v2.21.0 h1:lve4q/o/2rqwYOgUg3y3V2YPyD1/zkCLGjIV74Jit14= +github.com/hashicorp/hcl/v2 v2.21.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= +github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ= github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= @@ -91,6 +109,10 @@ github.com/hashicorp/terraform-plugin-go v0.23.0 h1:AALVuU1gD1kPb48aPQUjug9Ir/12 github.com/hashicorp/terraform-plugin-go v0.23.0/go.mod h1:1E3Cr9h2vMlahWMbsSEcNrOCxovCZhOOIXjFHbjc/lQ= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 h1:kJiWGx2kiQVo97Y5IOGR4EMcZ8DtMswHhUuFibsCQQE= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0/go.mod h1:sl/UoabMc37HA6ICVMmGO+/0wofkVIRxf+BMb/dnoIg= +github.com/hashicorp/terraform-plugin-testing v1.10.0 h1:2+tmRNhvnfE4Bs8rB6v58S/VpqzGC6RCh9Y8ujdn+aw= +github.com/hashicorp/terraform-plugin-testing v1.10.0/go.mod h1:iWRW3+loP33WMch2P/TEyCxxct/ZEcCGMquSLSCVsrc= github.com/hashicorp/terraform-registry-address v0.2.3 h1:2TAiKJ1A3MAkZlH1YI/aTVcLZRu7JseiXNRHbOAyoTI= github.com/hashicorp/terraform-registry-address v0.2.3/go.mod h1:lFHA76T8jfQteVfT7caREqguFrW3c4MFSPhZB7HHgUM= github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= @@ -108,8 +130,11 @@ github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgf github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -128,6 +153,10 @@ github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa1 github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= @@ -141,6 +170,8 @@ github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXq github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= +github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -158,6 +189,9 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= +github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= @@ -171,6 +205,8 @@ github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUei github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8= github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ= +github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw= go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -178,21 +214,29 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 h1:EDuYyU/MkFXllv9QF9819VlI9a4tzGuCbhG0ExK9o1U= golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= +golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -208,25 +252,37 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4= google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/client/api_test_helper.go b/internal/client/api_test_helper.go new file mode 100644 index 00000000..3fb64c88 --- /dev/null +++ b/internal/client/api_test_helper.go @@ -0,0 +1,30 @@ +package client + +import ( + "reflect" + "testing" +) + +type Case struct { + name string + value interface{} + expected interface{} +} + +func testCases(t *testing.T, cases []Case) { + for _, c := range cases { + value := c.value + if c.expected == nil { + if !reflect.ValueOf(value).IsNil() { + t.Errorf("Expected %s to be nil, got %v", c.name, value) + } + continue + } + if reflect.ValueOf(value).Kind() == reflect.Ptr { + value = reflect.ValueOf(value).Elem().Interface() + } + if c.expected != value { + t.Errorf("Expected %s to be %v, got %v", c.name, c.expected, value) + } + } +} diff --git a/internal/client/datamart_definition_test.go b/internal/client/datamart_definition_test.go index 028bd1c4..9f6db0ec 100644 --- a/internal/client/datamart_definition_test.go +++ b/internal/client/datamart_definition_test.go @@ -4,36 +4,9 @@ import ( "io" "net/http" "net/http/httptest" - "reflect" "testing" ) -// Helpers - -type Case struct { - name string - value interface{} - expected interface{} -} - -func testCases(t *testing.T, cases []Case) { - for _, c := range cases { - value := c.value - if c.expected == nil { - if !reflect.ValueOf(value).IsNil() { - t.Errorf("Expected %s to be nil, got %v", c.name, value) - } - continue - } - if reflect.ValueOf(value).Kind() == reflect.Ptr { - value = reflect.ValueOf(value).Elem().Interface() - } - if c.expected != value { - t.Errorf("Expected %s to be %v, got %v", c.name, c.expected, value) - } - } -} - // ListDatamartDefinitions func TestListDatamartDefinitions(t *testing.T) { diff --git a/internal/client/type.go b/internal/client/type.go index 6defaf61..22535d76 100644 --- a/internal/client/type.go +++ b/internal/client/type.go @@ -25,3 +25,15 @@ func (n NullableString) MarshalJSON() ([]byte, error) { } return json.Marshal(n.Value) } + +type NullableBool struct { + Value bool + Valid bool +} + +func (n NullableBool) MarshalJSON() ([]byte, error) { + if !n.Valid { + return []byte("null"), nil + } + return json.Marshal(n.Value) +} diff --git a/internal/client/user.go b/internal/client/user.go new file mode 100644 index 00000000..dd4a09b5 --- /dev/null +++ b/internal/client/user.go @@ -0,0 +1,151 @@ +package client + +import ( + "fmt" + "net/http" + "net/url" +) + +const BasePath = "/api/users" + +type User struct { + ID int64 `json:"id"` + Email string `json:"email"` + Role string `json:"role"` + CanUseAuditLog bool `json:"can_use_audit_log"` + IsRestrictedConnectionModify bool `json:"is_restricted_connection_modify"` + LastSignInAt string `json:"last_sign_in_at"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +// List of Users + +type ListUsersInput struct { + limit *int + cursor *string +} + +func (input *ListUsersInput) SetLimit(limit int) { + input.limit = &limit +} + +func (input *ListUsersInput) SetCursor(cursor string) { + input.cursor = &cursor +} + +type ListUsersOutput struct { + Items []User `json:"items"` + NextCursor *string `json:"next_cursor"` +} + +const MaxListUsersLimit = 100 + +func (client *TroccoClient) ListUsers(input *ListUsersInput) (*ListUsersOutput, error) { + params := url.Values{} + if input != nil && input.limit != nil { + if *input.limit < 1 || *input.limit > MaxListUsersLimit { + return nil, fmt.Errorf("limit must be between 1 and %d", MaxListUsersLimit) + } + params.Add("limit", fmt.Sprintf("%d", *input.limit)) + } + if input != nil && input.cursor != nil { + params.Add("cursor", *input.cursor) + } + path := fmt.Sprintf(BasePath+"?%s", params.Encode()) + output := new(ListUsersOutput) + err := client.do(http.MethodGet, path, nil, output) + if err != nil { + return nil, err + } + return output, nil +} + +// Get a User + +type GetUserOutput struct { + ID int64 `json:"id"` + Email string `json:"email"` + Role string `json:"role"` + CanUseAuditLog bool `json:"can_use_audit_log"` + IsRestrictedConnectionModify bool `json:"is_restricted_connection_modify"` + LastSignInAt string `json:"last_sign_in_at"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +func (client *TroccoClient) GetUser(id int64) (*GetUserOutput, error) { + path := fmt.Sprintf(BasePath+"/%d", id) + output := new(GetUserOutput) + err := client.do(http.MethodGet, path, nil, output) + if err != nil { + return nil, err + } + return output, nil +} + +// Create a User + +type CreateUserInput struct { + Email string `json:"email"` + Password string `json:"password"` + Role string `json:"role"` + CanUseAuditLog *bool `json:"can_use_audit_log"` + IsRestrictedConnectionModify *bool `json:"is_restricted_connection_modify"` +} + +type CreateUserOutput struct { + ID int64 `json:"id"` + Email string `json:"email"` + Role string `json:"role"` + CanUseAuditLog bool `json:"can_use_audit_log"` + IsRestrictedConnectionModify bool `json:"is_restricted_connection_modify"` + LastSignInAt string `json:"last_sign_in_at"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +func (client *TroccoClient) CreateUser(input *CreateUserInput) (*CreateUserOutput, error) { + output := new(CreateUserOutput) + err := client.do(http.MethodPost, BasePath, input, output) + if err != nil { + return nil, err + } + return output, nil +} + +// Update a User + +type UpdateUserInput struct { + Role *string `json:"role"` + CanUseAuditLog *bool `json:"can_use_audit_log"` + IsRestrictedConnectionModify *bool `json:"is_restricted_connection_modify"` +} + +type UpdateUserOutput struct { + ID int64 `json:"id"` + Email string `json:"email"` + Role string `json:"role"` + CanUseAuditLog bool `json:"can_use_audit_log"` + IsRestrictedConnectionModify bool `json:"is_restricted_connection_modify"` + LastSignInAt string `json:"last_sign_in_at"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +func (client *TroccoClient) UpdateUser(id int64, input *UpdateUserInput) (*UpdateUserOutput, error) { + path := fmt.Sprintf(BasePath+"/%d", id) + output := new(UpdateUserOutput) + err := client.do(http.MethodPatch, path, input, output) + if err != nil { + return nil, err + } + return output, nil +} + +// Delete a User + +func (client *TroccoClient) DeleteUser(id int64) error { + path := fmt.Sprintf(BasePath+"/%d", id) + return client.do(http.MethodDelete, path, nil, nil) +} diff --git a/internal/client/user_test.go b/internal/client/user_test.go new file mode 100644 index 00000000..7897469c --- /dev/null +++ b/internal/client/user_test.go @@ -0,0 +1,296 @@ +package client + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/samber/lo" +) + +// ListUsers + +func TestListUsers(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + cases := []Case{ + {"path", r.URL.Path, "/api/users"}, + {"method", r.Method, http.MethodGet}, + } + testCases(t, cases) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + resp := ` + { + "items": [ + { + "id": 1, + "email": "test1@example.com", + "role": "admin", + "can_use_audit_log": true, + "is_restricted_connection_modify": false, + "last_sign_in_at": "2024-07-29T19:00:00.000+09:00", + "created_at": "2024-07-29T19:00:00.000+09:00", + "updated_at": "2024-07-29T20:00:00.000+09:00" + }, + { + "id": 2, + "email": "test2@example.com", + "role": "member", + "can_use_audit_log": false, + "is_restricted_connection_modify": true, + "last_sign_in_at": "2024-07-29T19:00:00.000+09:00", + "created_at": "2024-07-29T21:00:00.000+09:00", + "updated_at": "2024-07-29T22:00:00.000+09:00" + } + ] + } + ` + _, err := w.Write([]byte(resp)) + if err != nil { + t.Errorf("Expected no error, got %s", err) + } + })) + defer server.Close() + + client := NewDevTroccoClient("1234567890", server.URL) + output, err := client.ListUsers(nil) + if err != nil { + t.Errorf("Expected no error, got %s", err) + } + if len(output.Items) != 2 { + t.Errorf("Expected output.Items to have 2 items, got %d", len(output.Items)) + } + cases := []Case{ + {"first item's ID", output.Items[0].ID, int64(1)}, + {"first item's email", output.Items[0].Email, "test1@example.com"}, + {"first item's role", output.Items[0].Role, "admin"}, + {"first item's can_use_audit_log", output.Items[0].CanUseAuditLog, true}, + {"first item's is_restricted_connection_modify", output.Items[0].IsRestrictedConnectionModify, false}, + {"first item's last_sign_in_at", output.Items[0].LastSignInAt, "2024-07-29T19:00:00.000+09:00"}, + {"first item's created_at", output.Items[0].CreatedAt, "2024-07-29T19:00:00.000+09:00"}, + {"first item's updated_at", output.Items[0].UpdatedAt, "2024-07-29T20:00:00.000+09:00"}, + {"second item's ID", output.Items[1].ID, int64(2)}, + {"second item's email", output.Items[1].Email, "test2@example.com"}, + {"second item's role", output.Items[1].Role, "member"}, + {"second item's can_use_audit_log", output.Items[1].CanUseAuditLog, false}, + {"second item's is_restricted_connection_modify", output.Items[1].IsRestrictedConnectionModify, true}, + {"second item's last_sign_in_at", output.Items[1].LastSignInAt, "2024-07-29T19:00:00.000+09:00"}, + {"second item's created_at", output.Items[1].CreatedAt, "2024-07-29T21:00:00.000+09:00"}, + {"second item's updated_at", output.Items[1].UpdatedAt, "2024-07-29T22:00:00.000+09:00"}, + } + testCases(t, cases) +} + +func TestListUsersLimitAndCursor(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + cases := []Case{ + {"query parameter limit", r.URL.Query().Get("limit"), "1"}, + {"query parameter cursor", r.URL.Query().Get("cursor"), "test_prev_cursor"}, + } + testCases(t, cases) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + resp := ` + { + "items": [], + "next_cursor": "test_next_cursor" + } + ` + _, err := w.Write([]byte(resp)) + if err != nil { + t.Errorf("Expected no error, got %s", err) + } + })) + defer server.Close() + + client := NewDevTroccoClient("1234567890", server.URL) + input := ListUsersInput{} + input.SetLimit(1) + input.SetCursor("test_prev_cursor") + output, err := client.ListUsers(&input) + if err != nil { + t.Errorf("Expected no error, got %s", err) + } + cases := []Case{ + {"next_cursor", *output.NextCursor, "test_next_cursor"}, + } + testCases(t, cases) +} + +// GetUser + +func TestGetUser(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + cases := []Case{ + {"path", r.URL.Path, "/api/users/1"}, + {"method", r.Method, http.MethodGet}, + } + testCases(t, cases) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + resp := ` + { + "id": 1, + "email": "test1@example.com", + "role": "admin", + "can_use_audit_log": true, + "is_restricted_connection_modify": false, + "last_sign_in_at": "2024-07-29T19:00:00.000+09:00", + "created_at": "2024-07-29T19:00:00.000+09:00", + "updated_at": "2024-07-29T20:00:00.000+09:00" + } + ` + _, err := w.Write([]byte(resp)) + if err != nil { + t.Errorf("Expected no error, got %s", err) + } + })) + defer server.Close() + + client := NewDevTroccoClient("1234567890", server.URL) + output, err := client.GetUser(1) + if err != nil { + t.Errorf("Expected no error, got %s", err) + } + cases := []Case{ + {"ID", output.ID, int64(1)}, + {"email", output.Email, "test1@example.com"}, + {"role", output.Role, "admin"}, + {"can_use_audit_log", output.CanUseAuditLog, true}, + {"is_restricted_connection_modify", output.IsRestrictedConnectionModify, false}, + {"last_sign_in_at", output.LastSignInAt, "2024-07-29T19:00:00.000+09:00"}, + {"created_at", output.CreatedAt, "2024-07-29T19:00:00.000+09:00"}, + {"updated_at", output.UpdatedAt, "2024-07-29T20:00:00.000+09:00"}, + } + testCases(t, cases) +} + +// CreateUser + +func TestCreateUser(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + cases := []Case{ + {"path", r.URL.Path, "/api/users"}, + {"method", r.Method, http.MethodPost}, + } + testCases(t, cases) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + resp := ` + { + "id": 1, + "email": "test@example.com", + "role": "admin", + "can_use_audit_log": true, + "is_restricted_connection_modify": false, + "last_sign_in_at": "2024-07-29T19:00:00.000+09:00", + "created_at": "2024-07-29T19:00:00.000+09:00", + "updated_at": "2024-07-29T20:00:00.000+09:00" + } + ` + _, err := w.Write([]byte(resp)) + if err != nil { + t.Errorf("Expected no error, got %s", err) + } + })) + defer server.Close() + + client := NewDevTroccoClient("1234567890", server.URL) + input := CreateUserInput{ + Email: "test@example.com", + Role: "admin", + CanUseAuditLog: lo.ToPtr(true), + IsRestrictedConnectionModify: lo.ToPtr(false), + } + output, err := client.CreateUser(&input) + if err != nil { + t.Errorf("Expected no error, got %s", err) + } + cases := []Case{ + {"ID", output.ID, int64(1)}, + {"email", output.Email, "test@example.com"}, + {"role", output.Role, "admin"}, + {"can_use_audit_log", output.CanUseAuditLog, true}, + {"is_restricted_connection_modify", output.IsRestrictedConnectionModify, false}, + {"last_sign_in_at", output.LastSignInAt, "2024-07-29T19:00:00.000+09:00"}, + {"created_at", output.CreatedAt, "2024-07-29T19:00:00.000+09:00"}, + {"updated_at", output.UpdatedAt, "2024-07-29T20:00:00.000+09:00"}, + } + testCases(t, cases) +} + +// UpdateUser + +func TestUpdateUser(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + cases := []Case{ + {"path", r.URL.Path, "/api/users/1"}, + {"method", r.Method, http.MethodPatch}, + } + testCases(t, cases) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + resp := ` + { + "id": 1, + "email": "test@example.com", + "role": "admin", + "can_use_audit_log": true, + "is_restricted_connection_modify": false, + "last_sign_in_at": "2024-07-29T19:00:00.000+09:00", + "created_at": "2024-07-29T19:00:00.000+09:00", + "updated_at": "2024-07-29T20:00:00.000+09:00" + } + ` + _, err := w.Write([]byte(resp)) + if err != nil { + t.Errorf("Expected no error, got %s", err) + } + })) + defer server.Close() + + client := NewDevTroccoClient("1234567890", server.URL) + input := UpdateUserInput{ + Role: lo.ToPtr("admin"), + CanUseAuditLog: lo.ToPtr(true), + IsRestrictedConnectionModify: lo.ToPtr(false), + } + output, err := client.UpdateUser(1, &input) + if err != nil { + t.Errorf("Expected no error, got %s", err) + } + cases := []Case{ + {"ID", output.ID, int64(1)}, + {"email", output.Email, "test@example.com"}, + {"role", output.Role, "admin"}, + {"can_use_audit_log", output.CanUseAuditLog, true}, + {"is_restricted_connection_modify", output.IsRestrictedConnectionModify, false}, + {"last_sign_in_at", output.LastSignInAt, "2024-07-29T19:00:00.000+09:00"}, + {"created_at", output.CreatedAt, "2024-07-29T19:00:00.000+09:00"}, + {"updated_at", output.UpdatedAt, "2024-07-29T20:00:00.000+09:00"}, + } + testCases(t, cases) + +} + +// DeleteUser + +func TestDeleteUser(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + cases := []Case{ + {"path", r.URL.Path, "/api/users/1"}, + {"method", r.Method, http.MethodDelete}, + } + testCases(t, cases) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusNoContent) + })) + defer server.Close() + + client := NewDevTroccoClient("1234567890", server.URL) + err := client.DeleteUser(1) + if err != nil { + t.Errorf("Expected no error, got %s", err) + } +} diff --git a/internal/provider/bigquery_datamart_definition_resource.go b/internal/provider/bigquery_datamart_definition_resource.go index 37e84312..5ecf9b6e 100644 --- a/internal/provider/bigquery_datamart_definition_resource.go +++ b/internal/provider/bigquery_datamart_definition_resource.go @@ -22,7 +22,7 @@ import ( var _ resource.Resource = &bigqueryDatamartDefinitionResource{} var _ resource.ResourceWithImportState = &bigqueryDatamartDefinitionResource{} -func newBigqueryDatamartDefinitionResource() resource.Resource { +func NewBigqueryDatamartDefinitionResource() resource.Resource { return &bigqueryDatamartDefinitionResource{} } @@ -797,7 +797,7 @@ func (r *bigqueryDatamartDefinitionResource) Update(ctx context.Context, req res model, err := parseToBigqueryDatamartDefinitionModel(data.DatamartDefinition) if err != nil { resp.Diagnostics.AddError( - "Parseing datamart definition", + "Parsing datamart definition", fmt.Sprintf("Unable to parse datamart definition (id: %d), got error: %s", state.ID.ValueInt64(), err), ) return diff --git a/internal/provider/ignore_changes_modifier.go b/internal/provider/ignore_changes_modifier.go new file mode 100644 index 00000000..6a0e204d --- /dev/null +++ b/internal/provider/ignore_changes_modifier.go @@ -0,0 +1,31 @@ +package provider + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" +) + +var _ planmodifier.String = &IgnoreChangesPlanModifier{} + +// IgnoreChangesPlanModifier is a plan modifier that ignores changes attribute after creation. +type IgnoreChangesPlanModifier struct{} + +func (m IgnoreChangesPlanModifier) Description(ctx context.Context) string { + return "Ignore changes to attribute after creation." +} + +func (m IgnoreChangesPlanModifier) MarkdownDescription(ctx context.Context) string { + return m.Description(ctx) +} + +func (m IgnoreChangesPlanModifier) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) { + // return if the resource is being created + if req.State.Raw.IsNull() { + return + } + + if req.PlanValue.IsUnknown() || req.StateValue.IsUnknown() || req.PlanValue == req.StateValue { + return + } + resp.PlanValue = req.StateValue +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 798828ac..95ec446c 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -131,7 +131,8 @@ func (p *TroccoProvider) Configure(ctx context.Context, req provider.ConfigureRe func (p *TroccoProvider) Resources(ctx context.Context) []func() resource.Resource { return []func() resource.Resource{ - newBigqueryDatamartDefinitionResource, + NewBigqueryDatamartDefinitionResource, + NewUserResource, } } diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go new file mode 100644 index 00000000..53e31170 --- /dev/null +++ b/internal/provider/provider_test.go @@ -0,0 +1,20 @@ +package provider + +import ( + "fmt" + "os" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" +) + +var ( + providerConfig = fmt.Sprintf(` + provider "trocco" { + dev_base_url = "%s" + } + `, os.Getenv("TROCCO_TEST_URL")) + testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){ + "trocco": providerserver.NewProtocol6WithError(New("test")()), + } +) diff --git a/internal/provider/require_on_create_plan_modifier.go b/internal/provider/require_on_create_plan_modifier.go new file mode 100644 index 00000000..75ecf05f --- /dev/null +++ b/internal/provider/require_on_create_plan_modifier.go @@ -0,0 +1,37 @@ +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" +) + +var _ planmodifier.String = &RequiredOnCreatePlanModifier{} + +// RequiredOnCreatePlanModifier is a plan modifier that ensures that a field is only required on resource creation. +type RequiredOnCreatePlanModifier struct { + AttributeName string +} + +func (m RequiredOnCreatePlanModifier) Description(ctx context.Context) string { + return "This field is required on resource creation." +} + +func (m RequiredOnCreatePlanModifier) MarkdownDescription(ctx context.Context) string { + return m.Description(ctx) +} + +func (m RequiredOnCreatePlanModifier) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) { + // Check if the resource is being created. + if !req.State.Raw.IsNull() { + return + } + if req.ConfigValue.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root(m.AttributeName), + "Missing Required Attribute", + "The attribute '"+m.AttributeName+"' is required on resource creation.", + ) + } +} diff --git a/internal/provider/user_resource.go b/internal/provider/user_resource.go new file mode 100644 index 00000000..d2c98c02 --- /dev/null +++ b/internal/provider/user_resource.go @@ -0,0 +1,271 @@ +package provider + +import ( + "context" + "fmt" + "regexp" + "strconv" + "terraform-provider-trocco/internal/client" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ resource.Resource = &userResource{} + _ resource.ResourceWithConfigure = &userResource{} + _ resource.ResourceWithImportState = &userResource{} +) + +func NewUserResource() resource.Resource { + return &userResource{} +} + +type userResource struct { + client *client.TroccoClient +} + +type userResourceModel struct { + ID types.Int64 `tfsdk:"id"` + Email types.String `tfsdk:"email"` + Password types.String `tfsdk:"password"` + Role types.String `tfsdk:"role"` + CanUseAuditLog types.Bool `tfsdk:"can_use_audit_log"` + IsRestrictedConnectionModify types.Bool `tfsdk:"is_restricted_connection_modify"` +} + +func (r *userResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_user" +} + +func (r *userResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*client.TroccoClient) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *client.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + r.client = client +} + +func (r *userResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Provides a TROCCO user resource.", + Attributes: map[string]schema.Attribute{ + "id": schema.Int64Attribute{ + Computed: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, + MarkdownDescription: "The ID of the user.", + }, + "email": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.RegexMatches( + regexp.MustCompile(`^[^@\s]+@[^@\s]+$`), + "invalid email address", + ), + }, + MarkdownDescription: "The email of the user.", + }, + "password": schema.StringAttribute{ + Optional: true, + Computed: true, + Sensitive: true, + PlanModifiers: []planmodifier.String{ + &IgnoreChangesPlanModifier{}, + &RequiredOnCreatePlanModifier{"password"}, + }, + Validators: []validator.String{ + // see: https://documents.trocco.io/docs/password-policy + stringvalidator.LengthBetween(8, 128), + stringvalidator.All( + stringvalidator.RegexMatches(regexp.MustCompile(`[a-zA-Z]`), "must contain at least one letter"), + stringvalidator.RegexMatches(regexp.MustCompile(`[0-9]`), "must contain at least one number"), + ), + }, + MarkdownDescription: "The password of the user. It must be at least 8 characters long and contain at least one letter and one number. It is required when creating a new user but optional during updates.", + }, + "role": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("super_admin", "admin", "member"), + }, + MarkdownDescription: "The role of the user. Valid value is `super_admin`, `admin`, or `member`.", + }, + "can_use_audit_log": schema.BoolAttribute{ + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.UseStateForUnknown(), + }, + MarkdownDescription: "Whether the user can use the audit log.", + }, + "is_restricted_connection_modify": schema.BoolAttribute{ + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.UseStateForUnknown(), + }, + MarkdownDescription: "Whether the user is restricted to modify connections.", + }, + }, + } +} + +func (r *userResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan userResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + input := client.CreateUserInput{ + Email: plan.Email.ValueString(), + Password: plan.Password.ValueString(), + Role: plan.Role.ValueString(), + } + if !plan.CanUseAuditLog.IsNull() { + input.CanUseAuditLog = plan.CanUseAuditLog.ValueBoolPointer() + } + if !plan.IsRestrictedConnectionModify.IsNull() { + input.IsRestrictedConnectionModify = plan.IsRestrictedConnectionModify.ValueBoolPointer() + } + + user, err := r.client.CreateUser(&input) + if err != nil { + resp.Diagnostics.AddError( + "Creating user", + fmt.Sprintf("Unable to create user, got error: %s", err), + ) + return + } + + data := userResourceModel{ + ID: types.Int64Value(user.ID), + Password: types.StringValue(plan.Password.ValueString()), + Email: types.StringValue(user.Email), + Role: types.StringValue(user.Role), + CanUseAuditLog: types.BoolValue(user.CanUseAuditLog), + IsRestrictedConnectionModify: types.BoolValue(user.IsRestrictedConnectionModify), + } + + resp.Diagnostics.Append(resp.State.Set(ctx, data)...) +} + +func (r *userResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state userResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + user, err := r.client.GetUser(state.ID.ValueInt64()) + if err != nil { + resp.Diagnostics.AddError( + "Reading user", + fmt.Sprintf("Unable to read user, got error: %s", err), + ) + return + } + + data := userResourceModel{ + ID: types.Int64Value(user.ID), + Password: types.StringValue(state.Password.ValueString()), + Email: types.StringValue(user.Email), + Role: types.StringValue(user.Role), + CanUseAuditLog: types.BoolValue(user.CanUseAuditLog), + IsRestrictedConnectionModify: types.BoolValue(user.IsRestrictedConnectionModify), + } + + resp.Diagnostics.Append(resp.State.Set(ctx, data)...) +} + +func (r *userResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state userResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + input := client.UpdateUserInput{} + if !plan.Role.IsNull() { + input.Role = plan.Role.ValueStringPointer() + } + if !plan.CanUseAuditLog.IsNull() { + input.CanUseAuditLog = plan.CanUseAuditLog.ValueBoolPointer() + } + if !plan.IsRestrictedConnectionModify.IsNull() { + input.IsRestrictedConnectionModify = plan.IsRestrictedConnectionModify.ValueBoolPointer() + } + + user, err := r.client.UpdateUser(state.ID.ValueInt64(), &input) + if err != nil { + resp.Diagnostics.AddError( + "Updating user", + fmt.Sprintf("Unable to update user, got error: %s", err), + ) + return + } + + data := userResourceModel{ + ID: types.Int64Value(user.ID), + Password: types.StringValue(state.Password.ValueString()), + Email: types.StringValue(user.Email), + Role: types.StringValue(user.Role), + CanUseAuditLog: types.BoolValue(user.CanUseAuditLog), + IsRestrictedConnectionModify: types.BoolValue(user.IsRestrictedConnectionModify), + } + + resp.Diagnostics.Append(resp.State.Set(ctx, data)...) +} + +func (r *userResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state userResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + err := r.client.DeleteUser(state.ID.ValueInt64()) + if err != nil { + resp.Diagnostics.AddError( + "Deleting user", + fmt.Sprintf("Unable to delete user, got error: %s", err), + ) + return + } +} + +func (r *userResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + id, err := strconv.ParseInt(req.ID, 10, 64) + if err != nil { + resp.Diagnostics.AddError( + "Importing user", + fmt.Sprintf("Unable to parse id, got error: %s", err), + ) + return + } + + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), id)...) +} diff --git a/internal/provider/user_resource_test.go b/internal/provider/user_resource_test.go new file mode 100644 index 00000000..e1f2eb75 --- /dev/null +++ b/internal/provider/user_resource_test.go @@ -0,0 +1,140 @@ +package provider + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccUserResource(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: providerConfig + ` + resource "trocco_user" "test" { + email = "test@example.com" + password = "3XRambMkp-Hw" + role = "admin" + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("trocco_user.test", "email", "test@example.com"), + resource.TestCheckResourceAttr("trocco_user.test", "role", "admin"), + resource.TestCheckResourceAttr("trocco_user.test", "can_use_audit_log", "false"), + resource.TestCheckResourceAttr("trocco_user.test", "is_restricted_connection_modify", "false"), + resource.TestCheckResourceAttrSet("trocco_user.test", "id"), + ), + }, + // ImportState testing + { + ResourceName: "trocco_user.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"password"}, + }, + // Update and Read testing + { + Config: providerConfig + ` + resource "trocco_user" "test" { + email = "test@example.com" + role = "member" + can_use_audit_log = true + is_restricted_connection_modify = true + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("trocco_user.test", "email", "test@example.com"), + resource.TestCheckResourceAttr("trocco_user.test", "role", "member"), + resource.TestCheckResourceAttr("trocco_user.test", "can_use_audit_log", "true"), + resource.TestCheckResourceAttr("trocco_user.test", "is_restricted_connection_modify", "true"), + ), + }, + }, + }) +} + +func TestAccUserResourceInvalidEmail(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: providerConfig + ` + resource "trocco_user" "test" { + email = "test" + password = "3XRambMkp-Hw" + role = "admin" + } + `, + ExpectError: regexp.MustCompile(`invalid email address`), + }, + }, + }) +} + +func TestAccUserResourceInvalidPassword(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: providerConfig + ` + resource "trocco_user" "test" { + email = "test@example.com" + password = "abc123" + role = "admin" + } + `, + ExpectError: regexp.MustCompile(`password string length`), + }, + { + Config: providerConfig + ` + resource "trocco_user" "test" { + email = "test@example.com" + password = "1111111111111" + role = "admin" + } + `, + ExpectError: regexp.MustCompile(`must contain at least one letter`), + }, + { + Config: providerConfig + ` + resource "trocco_user" "test" { + email = "test@example.com" + password = "aaaaaaaaaaaaa" + role = "admin" + } + `, + ExpectError: regexp.MustCompile(`must contain at least one number`), + }, + { + Config: providerConfig + ` + resource "trocco_user" "test" { + email = "test@example.com" + role = "admin" + } + `, + ExpectError: regexp.MustCompile(`Missing Required Attribute`), + }, + }, + }) +} + +func TestAccUserResourceInvalidRole(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: providerConfig + ` + resource "trocco_user" "test" { + email = "test@example.com" + password = "abc123" + role = "invalid role" + } + `, + ExpectError: regexp.MustCompile(`role value must be one of`), + }, + }, + }) +}