-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ci(e2e): Add end-to-end tests with the store and contracts backend mo…
…cks (#345) This PR augments the end to end tests by adding test cases for the purchasing workflow, following a similar recipe to the existing "organization provided" and 'manually input" token test scenarios. We add one more function to the `end_to_end_test.dart` file, handling the automation of the referred workflow, triggering a MS Store purchase action. That requires both the MS Store mock as well as the Contracts backend mock. Those are started by the test code (Go) prior to running the Ubuntu Pro for Windows app, ports are selected dynamically and the final addresses are supplied to the clients of the mock via environment variables. That, in turn, requires the MSIX to be built with the mock-aware store API, as well as a mock-aware CS backend URL (selected by a build tag). This is basically the contents I presented in one Desktop Team pulse review demo, but augmented with two simulated purchase failure scenarios (due CS backend outage or MS Store internal failure) and adapted to the changes introduced in #339 . One race condition in the storemockserver was found and fixed as part of this journey.
- Loading branch information
Showing
16 changed files
with
286 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
package endtoend_test | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
"time" | ||
|
||
"github.com/canonical/ubuntu-pro-for-windows/common/golden" | ||
"github.com/canonical/ubuntu-pro-for-windows/mocks/contractserver/contractsmockserver" | ||
"github.com/canonical/ubuntu-pro-for-windows/mocks/storeserver/storemockserver" | ||
"github.com/stretchr/testify/require" | ||
wsl "github.com/ubuntu/gowsl" | ||
"gopkg.in/yaml.v3" | ||
) | ||
|
||
const ( | ||
contractsEndpointEnv = "UP4W_CONTRACTS_BACKEND_MOCK_ENDPOINT" | ||
storeEndpointEnv = "UP4W_MS_STORE_MOCK_ENDPOINT" | ||
allowPurchaseEnvOverride = "UP4W_ALLOW_STORE_PURCHASE=1" | ||
) | ||
|
||
func TestPurchase(t *testing.T) { | ||
type whenToken int | ||
const ( | ||
never whenToken = iota | ||
beforeDistroRegistration | ||
afterDistroRegistration | ||
) | ||
|
||
// Let's be lazy and don't fall into the risk of changing the function name without updating the places where its name is used. | ||
currentFuncName := t.Name() | ||
|
||
testCases := map[string]struct { | ||
whenToStartAgent whenToken | ||
withToken string | ||
csServerDown bool | ||
storeDown bool | ||
|
||
wantAttached bool | ||
}{ | ||
"Success when applying pro token before registration": {whenToStartAgent: beforeDistroRegistration, wantAttached: true}, | ||
"Success when applying pro token after registration": {whenToStartAgent: afterDistroRegistration, wantAttached: true}, | ||
|
||
"Error due MS Store API failure": {whenToStartAgent: beforeDistroRegistration, storeDown: true}, | ||
"Error due Contracts Server Backend down": {whenToStartAgent: afterDistroRegistration, csServerDown: true}, | ||
} | ||
|
||
for name, tc := range testCases { | ||
tc := tc | ||
t.Run(name, func(t *testing.T) { | ||
testSetup(t) | ||
|
||
settings := contractsmockserver.DefaultSettings() | ||
|
||
token := os.Getenv(proTokenEnv) | ||
if tc.withToken != "" { | ||
token = tc.withToken | ||
} | ||
require.NotEmpty(t, token, "Setup: provide a Pro token either via UP4W_TEST_PRO_TOKEN environment variable or the test case struct withToken field") | ||
settings.Subscription.OnSuccess.Value = token | ||
|
||
cs := contractsmockserver.NewServer(settings) | ||
//nolint:errcheck // Nothing we can do about it | ||
defer cs.Stop() | ||
|
||
ctx := context.Background() | ||
contractsCtx, contractsCancel := context.WithCancel(ctx) | ||
defer contractsCancel() | ||
|
||
if !tc.csServerDown { | ||
err := cs.Serve(contractsCtx, "localhost:0") | ||
require.NoError(t, err, "Setup: Server should return no error") | ||
} | ||
|
||
contractsEndpointEnvOverride := fmt.Sprintf("%s=%s", contractsEndpointEnv, cs.Address()) | ||
|
||
testData, err := os.ReadFile(filepath.Join(golden.TestFamilyPath(t), "storemock_config.yaml")) | ||
require.NoError(t, err, "Setup: Could not read test fixture input file") | ||
|
||
storeSettings := storemockserver.DefaultSettings() | ||
err = yaml.Unmarshal(testData, &storeSettings) | ||
require.NoError(t, err, "Setup: Unmarshalling test data should return no error") | ||
|
||
store := storemockserver.NewServer(storeSettings) | ||
//nolint:errcheck // Nothing we can do about it | ||
defer store.Stop() | ||
|
||
storeCtx, storeCancel := context.WithCancel(ctx) | ||
defer storeCancel() | ||
|
||
if !tc.storeDown { | ||
err = store.Serve(storeCtx, "localhost:0") | ||
require.NoError(t, err, "Setup: Server should return no error") | ||
} | ||
|
||
storeEndpointEnvOverride := fmt.Sprintf("%s=%s", storeEndpointEnv, store.Address()) | ||
|
||
// Either runs the ubuntupro app before... | ||
if tc.whenToStartAgent == beforeDistroRegistration { | ||
cleanup := startAgent(t, ctx, currentFuncName, allowPurchaseEnvOverride, contractsEndpointEnvOverride, storeEndpointEnvOverride) | ||
defer cleanup() | ||
} | ||
|
||
// Distro setup | ||
name := registerFromTestImage(t, ctx) | ||
d := wsl.NewDistro(ctx, name) | ||
|
||
defer func() { | ||
if t.Failed() { | ||
logWslProServiceJournal(t, ctx, d) | ||
} | ||
}() | ||
|
||
out, err := d.Command(ctx, "exit 0").CombinedOutput() | ||
require.NoErrorf(t, err, "Setup: could not wake distro up: %v. %s", err, out) | ||
|
||
// ... or after registration, but never both. | ||
if tc.whenToStartAgent == afterDistroRegistration { | ||
cleanup := startAgent(t, ctx, currentFuncName, allowPurchaseEnvOverride, contractsEndpointEnvOverride, storeEndpointEnvOverride) | ||
defer cleanup() | ||
|
||
out, err = d.Command(ctx, "exit 0").CombinedOutput() | ||
require.NoErrorf(t, err, "Setup: could not wake distro up: %v. %s", err, out) | ||
} | ||
|
||
const maxTimeout = 30 * time.Second | ||
|
||
if !tc.wantAttached { | ||
time.Sleep(maxTimeout) | ||
proCtx, cancel := context.WithTimeout(ctx, maxTimeout) | ||
defer cancel() | ||
attached, err := distroIsProAttached(t, proCtx, d) | ||
require.NoError(t, err, "could not determine if distro is attached") | ||
require.False(t, attached, "distro should not have been Pro attached") | ||
return | ||
} | ||
|
||
require.Eventually(t, func() bool { | ||
attached, err := distroIsProAttached(t, ctx, d) | ||
if err != nil { | ||
t.Logf("could not determine if distro is attached: %v", err) | ||
} | ||
return attached | ||
}, maxTimeout, time.Second, "distro should have been Pro attached") | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
generateuserjwt: | ||
onsuccess: | ||
value: CPP_MOCK_JWT | ||
status: 200 | ||
allproducts: | ||
- storeid: 9P25B50XMKXT | ||
title: Annual Subscription (production) | ||
description: To this lovely mock of the production product ID | ||
isinusercollection: false | ||
productkind: Durable | ||
expirationdate: 0001-01-01T00:00:00Z | ||
- storeid: 9N9Q5G4QSMLS | ||
title: Monthly Subscription (created for testing, free) | ||
description: To this lovely mock of the production product ID | ||
isinusercollection: false | ||
productkind: Durable | ||
expirationdate: 0001-01-01T00:00:00Z | ||
- storeid: CPP_MOCK_CONSUMABLE | ||
title: Consume | ||
description: This mock is nice | ||
isinusercollection: false | ||
productkind: Consumable | ||
expirationdate: 0001-01-01T00:00:00Z | ||
- storeid: cannotpurchase | ||
title: Forbidden | ||
description: This product cannot be owned | ||
isinusercollection: false | ||
productkind: Durable | ||
expirationdate: 0001-01-01T00:00:00Z | ||
- storeid: servererror | ||
title: Also forbidden | ||
description: This product always break the server | ||
isinusercollection: false | ||
productkind: Durable | ||
expirationdate: 0001-01-01T00:00:00Z |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.