diff --git a/.github/workflows/qa.yaml b/.github/workflows/qa.yaml index 603a62875..1f5295b5e 100644 --- a/.github/workflows/qa.yaml +++ b/.github/workflows/qa.yaml @@ -20,7 +20,16 @@ jobs: fail-fast: false matrix: os: [ubuntu, windows] - subproject: ["agentapi", "contractsapi", "mocks", "windows-agent", "wsl-pro-service", "end-to-end", "common"] + subproject: [ + "agentapi", + "contractsapi", + "mocks", + "storeapi/go-wrapper/microsoftstore", + "windows-agent", + "wsl-pro-service", + "end-to-end", + "common", + ] exclude: - os: windows subproject: wsl-pro-service @@ -171,12 +180,16 @@ jobs: fail-fast: false matrix: os: [ubuntu, windows] - subproject: ["windows-agent", "wsl-pro-service", "common"] + subproject: ["storeapi/go-wrapper/microsoftstore", "windows-agent", "wsl-pro-service", "common"] exclude: - os: windows subproject: wsl-pro-service + # Excluded because common/i18n depends on gettext - os: ubuntu - subproject: common # Excluded because we need gettext installed + subproject: "common" + # Excluded because microsoftstore_test needs msbuild + - os: ubuntu + subproject: "storeapi/go-wrapper/microsoftstore" runs-on: ${{ matrix.os }}-latest steps: - name: Check out repository @@ -190,10 +203,10 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} # The Windows Store needs to be built. The tests do it automatically, but they need - # msbuild in the path + # msbuild in the path. - name: Set up MSBuild uses: microsoft/setup-msbuild@v1 - if: matrix.os == 'windows' && matrix.subproject == 'windows-agent' + if: matrix.subproject == 'storeapi/go-wrapper/microsoftstore' - name: Run tests shell: bash if: always() && !cancelled() diff --git a/go.work b/go.work index d028ce40b..94e848382 100644 --- a/go.work +++ b/go.work @@ -6,6 +6,7 @@ use ( ./contractsapi ./end-to-end ./mocks + ./storeapi/go-wrapper/microsoftstore ./tools ./windows-agent ./wsl-pro-service diff --git a/go.work.sum b/go.work.sum index b86856dd8..13691457c 100644 --- a/go.work.sum +++ b/go.work.sum @@ -324,7 +324,7 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/canonical/ubuntu-pro-for-windows v0.0.0-20230904124233-dd1d0e5164e2 h1:N5kF9buRrZ6h+F/Od5GXybHfOkkzPZw5B9lNKoIl05Q= github.com/canonical/ubuntu-pro-for-windows v0.0.0-20230904124233-dd1d0e5164e2/go.mod h1:qFE3nfycqhcaxyJdCv7uqMWmZdtu3DNV81ure93ia+M= -github.com/canonical/ubuntu-pro-for-windows/common v0.0.0-20230905125854-3f98ad6913ab/go.mod h1:c6h+GIDg8ZrFIfgQkvpIRpKbJ3mGNOTIjX4H4buEnD0= +github.com/canonical/ubuntu-pro-for-windows/contractsapi v0.0.0-20230906073426-449ca654e51d/go.mod h1:lqO8UB33LPVdfiMDMlc1swo3S9bwqhW8DtO75dgTLWo= github.com/canonical/ubuntu-pro-for-windows/wslserviceapi v0.0.0-20230322100459-1ec4d0175382/go.mod h1:IJphMXBES/d3n/pXddrP08VNMYaQM3edlKeV3GSxrXU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= diff --git a/mocks/go.sum b/mocks/go.sum index 16b7678c9..03839980d 100644 --- a/mocks/go.sum +++ b/mocks/go.sum @@ -31,6 +31,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= diff --git a/windows-agent/internal/contracts/microsoftstore/errors.go b/storeapi/go-wrapper/microsoftstore/errors.go similarity index 100% rename from windows-agent/internal/contracts/microsoftstore/errors.go rename to storeapi/go-wrapper/microsoftstore/errors.go diff --git a/windows-agent/internal/contracts/microsoftstore/find_dll_windows.go b/storeapi/go-wrapper/microsoftstore/find_dll_windows.go similarity index 100% rename from windows-agent/internal/contracts/microsoftstore/find_dll_windows.go rename to storeapi/go-wrapper/microsoftstore/find_dll_windows.go diff --git a/storeapi/go-wrapper/microsoftstore/go.mod b/storeapi/go-wrapper/microsoftstore/go.mod new file mode 100644 index 000000000..dd27154f5 --- /dev/null +++ b/storeapi/go-wrapper/microsoftstore/go.mod @@ -0,0 +1,17 @@ +module github.com/canonical/ubuntu-pro-for-windows/storeapi/go-wrapper/microsoftstore + +go 1.21.0 + +require ( + github.com/canonical/ubuntu-pro-for-windows/common v0.0.0-20230905125854-3f98ad6913ab + github.com/stretchr/testify v1.8.4 + github.com/ubuntu/decorate v0.0.0-20230905131025-e968fa48a85c + golang.org/x/sys v0.12.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/storeapi/go-wrapper/microsoftstore/go.sum b/storeapi/go-wrapper/microsoftstore/go.sum new file mode 100644 index 000000000..1a3e6cb66 --- /dev/null +++ b/storeapi/go-wrapper/microsoftstore/go.sum @@ -0,0 +1,23 @@ +github.com/canonical/ubuntu-pro-for-windows/common v0.0.0-20230905125854-3f98ad6913ab h1:Bg++brh/24o8Rz22GejFFsQAd2lS6k/MfixSiWIPQXE= +github.com/canonical/ubuntu-pro-for-windows/common v0.0.0-20230905125854-3f98ad6913ab/go.mod h1:c6h+GIDg8ZrFIfgQkvpIRpKbJ3mGNOTIjX4H4buEnD0= +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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/ubuntu/decorate v0.0.0-20230905131025-e968fa48a85c h1:jO41xNLddTDkrfz4w4RCMWCmX8Y+ZHz5jSbJWNLDvqU= +github.com/ubuntu/decorate v0.0.0-20230905131025-e968fa48a85c/go.mod h1:edGgz97NOqS2oqzbKrZqO9YU9neosRrkEZbVJVQynAA= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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= diff --git a/windows-agent/internal/contracts/microsoftstore/store_linux.go b/storeapi/go-wrapper/microsoftstore/store_linux.go similarity index 100% rename from windows-agent/internal/contracts/microsoftstore/store_linux.go rename to storeapi/go-wrapper/microsoftstore/store_linux.go diff --git a/windows-agent/internal/contracts/microsoftstore/store_test.go b/storeapi/go-wrapper/microsoftstore/store_test.go similarity index 89% rename from windows-agent/internal/contracts/microsoftstore/store_test.go rename to storeapi/go-wrapper/microsoftstore/store_test.go index 82e862118..6511b8a8b 100644 --- a/windows-agent/internal/contracts/microsoftstore/store_test.go +++ b/storeapi/go-wrapper/microsoftstore/store_test.go @@ -3,6 +3,7 @@ package microsoftstore_test import ( "context" "fmt" + "log/slog" "os" "os/exec" "path/filepath" @@ -11,17 +12,22 @@ import ( "time" "github.com/canonical/ubuntu-pro-for-windows/common" - "github.com/canonical/ubuntu-pro-for-windows/windows-agent/internal/contracts/microsoftstore" - log "github.com/canonical/ubuntu-pro-for-windows/windows-agent/internal/grpc/logstreamer" + "github.com/canonical/ubuntu-pro-for-windows/storeapi/go-wrapper/microsoftstore" "github.com/stretchr/testify/require" ) func TestMain(m *testing.M) { ctx := context.Background() + h := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ + Level: slog.LevelInfo, + }) + + slog.SetDefault(slog.New(h)) + if runtime.GOOS == "windows" { if err := buildStoreAPI(ctx); err != nil { - fmt.Fprintf(os.Stderr, "Setup: %v", err) + slog.Error(fmt.Sprintf("Setup: %v", err)) os.Exit(1) } } @@ -102,13 +108,13 @@ func buildStoreAPI(ctx context.Context) error { `-verbosity:normal`, ) - log.Infof(ctx, "Building store api DLL") + slog.Info("Building store api DLL") if out, err := cmd.CombinedOutput(); err != nil { return fmt.Errorf("could not build store api DLL: %v. Log:\n%s", err, out) } - log.Infof(ctx, "Built store api DLL") + slog.Info("Built store api DLL") return nil } diff --git a/windows-agent/internal/contracts/microsoftstore/store_windows.go b/storeapi/go-wrapper/microsoftstore/store_windows.go similarity index 100% rename from windows-agent/internal/contracts/microsoftstore/store_windows.go rename to storeapi/go-wrapper/microsoftstore/store_windows.go diff --git a/windows-agent/go.mod b/windows-agent/go.mod index cdca6df41..03a34368d 100644 --- a/windows-agent/go.mod +++ b/windows-agent/go.mod @@ -8,6 +8,7 @@ require ( github.com/canonical/ubuntu-pro-for-windows/common v0.0.0-20230906090052-60fb5d60ada4 github.com/canonical/ubuntu-pro-for-windows/contractsapi v0.0.0-20230906090052-60fb5d60ada4 github.com/canonical/ubuntu-pro-for-windows/mocks v0.0.0-20230906090052-60fb5d60ada4 + github.com/canonical/ubuntu-pro-for-windows/storeapi/go-wrapper/microsoftstore v0.0.0-20230906093332-11c8e171f61b github.com/canonical/ubuntu-pro-for-windows/wslserviceapi v0.0.0-20230906090052-60fb5d60ada4 github.com/golang/protobuf v1.5.3 github.com/google/uuid v1.3.1 @@ -15,7 +16,7 @@ require ( github.com/spf13/cobra v1.7.0 github.com/spf13/viper v1.16.0 github.com/stretchr/testify v1.8.4 - github.com/ubuntu/decorate v0.0.0-20230606064312-bc4ac83958d6 + github.com/ubuntu/decorate v0.0.0-20230905131025-e968fa48a85c github.com/ubuntu/gowsl v0.0.0-20230710120903-fcfd527a92e4 golang.org/x/exp v0.0.0-20230321023759-10a507213a29 golang.org/x/sys v0.12.0 diff --git a/windows-agent/go.sum b/windows-agent/go.sum index d5b726325..0c38a547c 100644 --- a/windows-agent/go.sum +++ b/windows-agent/go.sum @@ -50,6 +50,8 @@ github.com/canonical/ubuntu-pro-for-windows/contractsapi v0.0.0-20230906090052-6 github.com/canonical/ubuntu-pro-for-windows/contractsapi v0.0.0-20230906090052-60fb5d60ada4/go.mod h1:lqO8UB33LPVdfiMDMlc1swo3S9bwqhW8DtO75dgTLWo= github.com/canonical/ubuntu-pro-for-windows/mocks v0.0.0-20230906090052-60fb5d60ada4 h1:V6mnKw1LBnVyWAa6/G6uumbpL8wrhI1LZu4nJbc9nCk= github.com/canonical/ubuntu-pro-for-windows/mocks v0.0.0-20230906090052-60fb5d60ada4/go.mod h1:9Y+yTNgzog0o2y8UABpKjH9lCdlHUAVMPEV5jvdFhqU= +github.com/canonical/ubuntu-pro-for-windows/storeapi/go-wrapper/microsoftstore v0.0.0-20230906093332-11c8e171f61b h1:6jBgx3Zg4/hrl3N8OT0tazWRxTP7J0XS+EJfSiV6R/w= +github.com/canonical/ubuntu-pro-for-windows/storeapi/go-wrapper/microsoftstore v0.0.0-20230906093332-11c8e171f61b/go.mod h1:WXLeU3sdojsfq0A/aI98bY1Bx/9JdrV7y14pNyXCtqo= github.com/canonical/ubuntu-pro-for-windows/wslserviceapi v0.0.0-20230906090052-60fb5d60ada4 h1:vvFUurP/iXkNIRJG6kAHffAXLGQB3R3PzbEeEg2RY3Q= github.com/canonical/ubuntu-pro-for-windows/wslserviceapi v0.0.0-20230906090052-60fb5d60ada4/go.mod h1:GEtgc3YqISt1slWIVqdIz2Wh7mrWLIyFbsug3+m180Y= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -203,8 +205,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/ubuntu/decorate v0.0.0-20230606064312-bc4ac83958d6 h1:J0625LLHcZxxnnKCdr2iBbTtYjUiv6KkM6NpGisuQ3Q= -github.com/ubuntu/decorate v0.0.0-20230606064312-bc4ac83958d6/go.mod h1:edGgz97NOqS2oqzbKrZqO9YU9neosRrkEZbVJVQynAA= +github.com/ubuntu/decorate v0.0.0-20230905131025-e968fa48a85c h1:jO41xNLddTDkrfz4w4RCMWCmX8Y+ZHz5jSbJWNLDvqU= +github.com/ubuntu/decorate v0.0.0-20230905131025-e968fa48a85c/go.mod h1:edGgz97NOqS2oqzbKrZqO9YU9neosRrkEZbVJVQynAA= github.com/ubuntu/gowsl v0.0.0-20230710120903-fcfd527a92e4 h1:fci3jnfZQxfb92PRQwJHXR7Mxj5OiIWXVxkkpiXrkQI= github.com/ubuntu/gowsl v0.0.0-20230710120903-fcfd527a92e4/go.mod h1:PBcZk1oAKQn1fwC9d9G2Vu25Ypxab9+UZMaPiPNjDy8= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/windows-agent/internal/contracts/contracts.go b/windows-agent/internal/contracts/contracts.go index 73ab8e5ce..85e27162c 100644 --- a/windows-agent/internal/contracts/contracts.go +++ b/windows-agent/internal/contracts/contracts.go @@ -8,15 +8,16 @@ import ( "net/url" "time" + "github.com/canonical/ubuntu-pro-for-windows/storeapi/go-wrapper/microsoftstore" "github.com/canonical/ubuntu-pro-for-windows/windows-agent/internal/contracts/contractclient" - "github.com/canonical/ubuntu-pro-for-windows/windows-agent/internal/contracts/microsoftstore" "github.com/ubuntu/decorate" ) const defaultProURL = "https://contracts.canonical.com" type options struct { - proURL *url.URL + proURL *url.URL + microsoftStore MicrosoftStore } // Option is an optional argument for ProToken. @@ -29,11 +30,37 @@ func WithProURL(proURL *url.URL) Option { } } +// WithMockMicrosoftStore overrides the storeAPI-backed Microsoft Store. +func WithMockMicrosoftStore(store MicrosoftStore) Option { + return func(o *options) { + o.microsoftStore = store + } +} + +// MicrosoftStore is an interface to the Microsoft store API. +type MicrosoftStore interface { + GenerateUserJWT(azureADToken string) (jwt string, err error) + GetSubscriptionExpirationDate() (tm time.Time, err error) +} + +// msftStoreDLL is the Microsoft Store backed by the storeapi DLL. +type msftStoreDLL struct{} + +func (msftStoreDLL) GenerateUserJWT(azureADToken string) (jwt string, err error) { + return microsoftstore.GenerateUserJWT(azureADToken) +} + +func (msftStoreDLL) GetSubscriptionExpirationDate() (tm time.Time, err error) { + return microsoftstore.GetSubscriptionExpirationDate() +} + // ProToken directs the dance between the Microsoft Store and the Ubuntu Pro contract server to // validate a store entitlement and obtain its associated pro token. If there is no entitlement, // the token is returned as an empty string. func ProToken(ctx context.Context, args ...Option) (token string, err error) { - var opts options + opts := options{ + microsoftStore: msftStoreDLL{}, + } for _, f := range args { f(&opts) @@ -49,7 +76,7 @@ func ProToken(ctx context.Context, args ...Option) (token string, err error) { contractClient := contractclient.New(opts.proURL, &http.Client{Timeout: 30 * time.Second}) - token, err = proToken(ctx, contractClient) + token, err = proToken(ctx, contractClient, opts.microsoftStore) if err != nil { return "", err } @@ -57,10 +84,10 @@ func ProToken(ctx context.Context, args ...Option) (token string, err error) { return token, nil } -func proToken(ctx context.Context, serverClient *contractclient.Client) (proToken string, err error) { +func proToken(ctx context.Context, serverClient *contractclient.Client, msftStore MicrosoftStore) (proToken string, err error) { defer decorate.OnError(&err, "could not obtain pro token") - expiration, err := microsoftstore.GetSubscriptionExpirationDate() + expiration, err := msftStore.GetSubscriptionExpirationDate() if err != nil { return "", fmt.Errorf("could not get subscription expiration date: %v", err) } @@ -74,7 +101,7 @@ func proToken(ctx context.Context, serverClient *contractclient.Client) (proToke return "", err } - storeToken, err := microsoftstore.GenerateUserJWT(adToken) + storeToken, err := msftStore.GenerateUserJWT(adToken) if err != nil { return "", err } diff --git a/windows-agent/internal/contracts/contracts_test.go b/windows-agent/internal/contracts/contracts_test.go new file mode 100644 index 000000000..8b7d41f15 --- /dev/null +++ b/windows-agent/internal/contracts/contracts_test.go @@ -0,0 +1,121 @@ +package contracts_test + +import ( + "context" + "errors" + "fmt" + "net/url" + "testing" + "time" + + "github.com/canonical/ubuntu-pro-for-windows/mocks/contractserver/contractsmockserver" + "github.com/canonical/ubuntu-pro-for-windows/windows-agent/internal/contracts" + "github.com/stretchr/testify/require" +) + +func TestProToken(t *testing.T) { + t.Parallel() + + //nolint:gosec // These are not real tokens + const ( + azureADToken = "AZURE_AD_TOKEN" + ubuntuProToken = "UBUNTU_PRO_TOKEN" + ) + + testCases := map[string]struct { + // Microsoft store + expired bool + jwtError bool + expDateError bool + + // Contract server + getServerAccessTokenErr bool + getProTokenErr bool + + wantErr bool + }{ + "Success": {}, + + "Error when the subscription has expired": {expired: true, wantErr: true}, + "Error when the store's GenerateUserJWT fails": {jwtError: true, wantErr: true}, + "Error when the store's GetSubscriptionExpirationDate fails": {expDateError: true, wantErr: true}, + "Error when the contract server's GetServerAccessToken fails": {getServerAccessTokenErr: true, wantErr: true}, + "Error when the contract server's GetProToken fails": {getProTokenErr: true, wantErr: true}, + } + + for name, tc := range testCases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + store := mockMSStore{ + expirationDate: time.Now().Add(24 * 365 * time.Hour), // Next year + expirationDateErr: tc.expDateError, + + jwt: "JWT_123", + jwtWantADToken: azureADToken, + jwtErr: tc.jwtError, + } + + if tc.expired { + store.expirationDate = time.Now().Add(-24 * 365 * time.Hour) // Last year + } + + settings := contractsmockserver.DefaultSettings() + + settings.Token.OnSuccess.Value = azureADToken + settings.Subscription.OnSuccess.Value = ubuntuProToken + + settings.Token.Disabled = tc.getServerAccessTokenErr + settings.Subscription.Disabled = tc.getProTokenErr + + server := contractsmockserver.NewServer(settings) + addr, err := server.Serve(ctx) + require.NoError(t, err, "Setup: Server should return no error") + //nolint:errcheck // Nothing we can do about it + defer server.Stop() + + url, err := url.Parse(fmt.Sprintf("http://%s", addr)) + require.NoError(t, err, "Setup: Server URL should have been parsed with no issues") + + token, err := contracts.ProToken(ctx, contracts.WithProURL(url), contracts.WithMockMicrosoftStore(store)) + if tc.wantErr { + require.Error(t, err, "ProToken should return an error") + return + } + require.NoError(t, err, "ProToken should return no error") + + require.Equal(t, ubuntuProToken, token, "Unexpected value for the pro token") + }) + } +} + +type mockMSStore struct { + jwt string + jwtWantADToken string + jwtErr bool + + expirationDate time.Time + expirationDateErr bool +} + +func (s mockMSStore) GenerateUserJWT(azureADToken string) (jwt string, err error) { + if s.jwtErr { + return "", errors.New("mock error") + } + + if azureADToken != s.jwtWantADToken { + return "", fmt.Errorf("Azure AD token does not match. Want %q and got %q", s.jwtWantADToken, azureADToken) + } + + return s.jwt, nil +} + +func (s mockMSStore) GetSubscriptionExpirationDate() (tm time.Time, err error) { + if s.expirationDateErr { + return time.Time{}, errors.New("mock error") + } + + return s.expirationDate, nil +}