From c216fece98d7831da58ed3f1d62294e4b993196d Mon Sep 17 00:00:00 2001 From: Alastair Flynn Date: Thu, 18 Apr 2024 10:45:18 +0100 Subject: [PATCH] Add unit tests --- Makefile | 4 +- go.mod | 1 + go.sum | 2 + internal/provider/resource_application.go | 2 +- internal/provider/validator_channel.go | 17 +++-- internal/provider/validator_channel_test.go | 73 +++++++++++++++++++++ 6 files changed, 92 insertions(+), 7 deletions(-) create mode 100644 internal/provider/validator_channel_test.go diff --git a/Makefile b/Makefile index 6c745207..0b0cd02f 100644 --- a/Makefile +++ b/Makefile @@ -67,7 +67,9 @@ juju-unit-test: .PHONY: envtestlxd envtestlxd: ## envtestlxd: Under development - Include env var and run unit tests against lxd - JUJU_CONTROLLER_ADDRESSES=${CONTROLLER_ADDRESSES} JUJU_USERNAME=${USERNAME} JUJU_PASSWORD=${PASSWORD} JUJU_CA_CERT=${CA_CERT} TF_ACC=1 TEST_CLOUD=lxd go test ./... -v $(TESTARGS) -timeout 120m + JUJU_CONTROLLER_ADDRESSES=${CONTROLLER_ADDRESSES} \ + JUJU_USERNAME=${USERNAME} JUJU_PASSWORD=${PASSWORD} \ + JUJU_CA_CERT=${CA_CERT} TF_ACC=1 TEST_CLOUD=lxd go test ./... -v $(TESTARGS) -timeout 120m .PHONY: testlxd testlxd: diff --git a/go.mod b/go.mod index 859603d6..58241128 100644 --- a/go.mod +++ b/go.mod @@ -113,6 +113,7 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/juju/ansiterm v1.0.0 // indirect github.com/juju/blobstore/v3 v3.0.2 // indirect + github.com/juju/charm/v11 v11.1.0 // indirect github.com/juju/description/v5 v5.0.4 // indirect github.com/juju/featureflag v1.0.0 // indirect github.com/juju/gnuflag v1.0.0 // indirect diff --git a/go.sum b/go.sum index 2a12251d..719c75e4 100644 --- a/go.sum +++ b/go.sum @@ -324,6 +324,8 @@ github.com/juju/ansiterm v1.0.0 h1:gmMvnZRq7JZJx6jkfSq9/+2LMrVEwGwt7UR6G+lmDEg= github.com/juju/ansiterm v1.0.0/go.mod h1:PyXUpnI3olx3bsPcHt98FGPX/KCFZ1Fi+hw1XLI6384= github.com/juju/blobstore/v3 v3.0.2 h1:roZ4YBuZYmWId6y/6ZLQSAMmNlHOclHD8PQAMOQer6E= github.com/juju/blobstore/v3 v3.0.2/go.mod h1:NXEgMhrVH5744/zLfSkzsySlDQUpCgzvcNxjJJhICko= +github.com/juju/charm/v11 v11.1.0 h1:YTvFRugIhRMAe4z6Vr7Acw9oKnJBNfpwN9yTOqJv3r0= +github.com/juju/charm/v11 v11.1.0/go.mod h1:Mge5Ko3pPgocmk4v1pQgmBhF8BuBLGTCFu3jq83JvHk= github.com/juju/charm/v12 v12.0.0 h1:/h3YRMqbgxT89QkQGgMS/myOxuHy/kzBLCDOvodsoFY= github.com/juju/charm/v12 v12.0.0/go.mod h1:rX3no84EHT+qN+BGtwqPyvueC1Sxr0bXWxsbUd6i1iY= github.com/juju/clock v1.0.3 h1:yJHIsWXeU8j3QcBdiess09SzfiXRRrsjKPn2whnMeds= diff --git a/internal/provider/resource_application.go b/internal/provider/resource_application.go index c4151d6c..89703d6c 100644 --- a/internal/provider/resource_application.go +++ b/internal/provider/resource_application.go @@ -240,7 +240,7 @@ func (r *applicationResource) Schema(_ context.Context, _ resource.SchemaRequest stringplanmodifier.UseStateForUnknown(), }, Validators: []validator.String{ - stringIsChannelValidator{}, + StringIsChannelValidator{}, }, }, "revision": schema.Int64Attribute{ diff --git a/internal/provider/validator_channel.go b/internal/provider/validator_channel.go index 53eb063e..b747b9a8 100644 --- a/internal/provider/validator_channel.go +++ b/internal/provider/validator_channel.go @@ -10,26 +10,33 @@ import ( "github.com/juju/charm/v11" ) -type stringIsChannelValidator struct{} +type StringIsChannelValidator struct{} // Description returns a plain text description of the validator's behavior, suitable for a practitioner to understand its impact. -func (v stringIsChannelValidator) Description(context.Context) string { +func (v StringIsChannelValidator) Description(context.Context) string { return "string must conform to track/risk or track/risk/branch e.g. latest/stable" } // MarkdownDescription returns a markdown formatted description of the validator's behavior, suitable for a practitioner to understand its impact. -func (v stringIsChannelValidator) MarkdownDescription(context.Context) string { +func (v StringIsChannelValidator) MarkdownDescription(context.Context) string { return "string must conform to track/risk or track/risk/branch e.g. latest/stable" } // Validate runs the main validation logic of the validator, reading configuration data out of `req` and updating `resp` with diagnostics. -func (v stringIsChannelValidator) ValidateString(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) { +func (v StringIsChannelValidator) ValidateString(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) { // If the value is unknown or null, there is nothing to validate. if req.ConfigValue.IsUnknown() || req.ConfigValue.IsNull() { return } - if channel, err := charm.ParseChannel(req.ConfigValue.ValueString()); err != nil || channel.Track == "" || channel.Risk == "" { + if channel, err := charm.ParseChannel(req.ConfigValue.ValueString()); err != nil { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Channel", + err.Error(), + ) + return + } else if channel.Track == "" || channel.Risk == "" { resp.Diagnostics.AddAttributeError( req.Path, "Invalid Channel", diff --git a/internal/provider/validator_channel_test.go b/internal/provider/validator_channel_test.go new file mode 100644 index 00000000..980bc75e --- /dev/null +++ b/internal/provider/validator_channel_test.go @@ -0,0 +1,73 @@ +// Copyright 2024 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package provider_test + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/juju/terraform-provider-juju/internal/provider" +) + +func TestChannelValidatorValid(t *testing.T) { + validChannels := []types.String{ + types.StringValue("track/stable"), + types.StringValue("track/edge/branch"), + types.StringNull(), + types.StringUnknown(), + } + + channelValidator := provider.StringIsChannelValidator{} + for _, channel := range validChannels { + req := validator.StringRequest{ + ConfigValue: channel, + } + var resp validator.StringResponse + channelValidator.ValidateString(context.Background(), req, &resp) + + if resp.Diagnostics.HasError() { + t.Errorf("errors %v", resp.Diagnostics.Errors()) + } + } +} + +func TestChannelValidatorInvalid(t *testing.T) { + invalidChannels := []struct { + str types.String + err string + }{{ + str: types.StringValue("track"), + err: "String must conform to track/risk or track/risk/branch, e.g. latest/stable", + }, { + str: types.StringValue("edge"), + err: "String must conform to track/risk or track/risk/branch, e.g. latest/stable", + }, { + str: types.StringValue(`track\risk`), + err: "String must conform to track/risk or track/risk/branch, e.g. latest/stable", + }, { + str: types.StringValue(`track/invalidrisk`), + err: `risk in channel "track/invalidrisk" not valid`, + }, { + str: types.StringValue(`track/invalidrisk/branch`), + err: `risk in channel "track/invalidrisk/branch" not valid`, + }} + + channelValidator := provider.StringIsChannelValidator{} + for _, test := range invalidChannels { + req := validator.StringRequest{ + ConfigValue: test.str, + } + var resp validator.StringResponse + channelValidator.ValidateString(context.Background(), req, &resp) + + if c := resp.Diagnostics.ErrorsCount(); c != 1 { + t.Errorf("expected one error, got %d", c) + } + if deets := resp.Diagnostics.Errors()[0].Detail(); deets != test.err { + t.Errorf("expected error %q, got %q", test.err, deets) + } + } +}