diff --git a/.github/workflows/qa-azure.yaml b/.github/workflows/qa-azure.yaml index aa5055e14..696babec8 100644 --- a/.github/workflows/qa-azure.yaml +++ b/.github/workflows/qa-azure.yaml @@ -78,6 +78,8 @@ jobs: Import-PfxCertificate -Password $pwd -CertStoreLocation Cert:CurrentUser\My -FilePath certificate\certificate.pfx - name: Build Bundle run: | + $env:UP4W_TEST_WITH_MS_STORE_MOCK=${{ matrix.mode == 'end_to_end_tests' && '1' || '$null'}} + msbuild ` .\msix\msix.sln ` -target:Build ` diff --git a/end-to-end/go.mod b/end-to-end/go.mod index 4f57baf46..d66519038 100644 --- a/end-to-end/go.mod +++ b/end-to-end/go.mod @@ -4,19 +4,23 @@ go 1.21.3 require ( github.com/canonical/ubuntu-pro-for-windows/common v0.0.0-20230906090052-60fb5d60ada4 + github.com/canonical/ubuntu-pro-for-windows/mocks v0.0.0-20231018132816-d78b12b1d065 github.com/stretchr/testify v1.8.4 github.com/ubuntu/decorate v0.0.0-20230905131025-e968fa48a85c github.com/ubuntu/gowsl v0.0.0-20231004124730-8fd8df02f394 golang.org/x/sys v0.13.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( + github.com/canonical/ubuntu-pro-for-windows/contractsapi v0.0.0-20230906090052-60fb5d60ada4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/google/uuid v1.3.1 // indirect - github.com/kr/text v0.2.0 // indirect - github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + github.com/spf13/cobra v1.7.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/end-to-end/go.sum b/end-to-end/go.sum index d6efa5e28..106d55cfc 100644 --- a/end-to-end/go.sum +++ b/end-to-end/go.sum @@ -2,15 +2,19 @@ github.com/0xrawsec/golang-utils v1.3.2 h1:ww4jrtHRSnX9xrGzJYbalx5nXoZewy4zPxiY+ github.com/0xrawsec/golang-utils v1.3.2/go.mod h1:m7AzHXgdSAkFCD9tWWsApxNVxMlyy7anpPVOyT/yM7E= github.com/canonical/ubuntu-pro-for-windows/common v0.0.0-20230906090052-60fb5d60ada4 h1:8gmzKOf7uRSXl+WfGcQNhf9Ua3Xo5eJzo+6G9nNrXTg= github.com/canonical/ubuntu-pro-for-windows/common v0.0.0-20230906090052-60fb5d60ada4/go.mod h1:vNexvsl8a0qQmLwFcmF2dPur0FH4jmMJAPH2VnXN+I0= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/canonical/ubuntu-pro-for-windows/contractsapi v0.0.0-20230906090052-60fb5d60ada4 h1:0jVDQ6uSC6scEXX6e83sD6Rw6KhD8/7Vr4Ha1ob2CHw= +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-20231018132816-d78b12b1d065 h1:TZ3D+XHolu1WV9VbEhyJw7kpDWF2LnbEVR0KXifcdIg= +github.com/canonical/ubuntu-pro-for-windows/mocks v0.0.0-20231018132816-d78b12b1d065/go.mod h1:A99+dgUvd12iHGuZKui/Eol0fV3Uc4oOrSzwSKI+/mA= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= @@ -18,8 +22,13 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +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/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= @@ -28,12 +37,16 @@ github.com/ubuntu/decorate v0.0.0-20230905131025-e968fa48a85c h1:jO41xNLddTDkrfz github.com/ubuntu/decorate v0.0.0-20230905131025-e968fa48a85c/go.mod h1:edGgz97NOqS2oqzbKrZqO9YU9neosRrkEZbVJVQynAA= github.com/ubuntu/gowsl v0.0.0-20231004124730-8fd8df02f394 h1:DS9wb53gTUxFCPYnhAqOhfdRLsHTL9NpzT+F/D8NwIg= github.com/ubuntu/gowsl v0.0.0-20231004124730-8fd8df02f394/go.mod h1:gu6CgOaqMFFz1h96UocQwXvRvF6CePIqQnI58DzIg2Q= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/end-to-end/purchase_test.go b/end-to-end/purchase_test.go new file mode 100644 index 000000000..0efadc1fb --- /dev/null +++ b/end-to-end/purchase_test.go @@ -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") + }) + } +} diff --git a/end-to-end/testdata/TestPurchase/storemock_config.yaml b/end-to-end/testdata/TestPurchase/storemock_config.yaml new file mode 100644 index 000000000..75968211f --- /dev/null +++ b/end-to-end/testdata/TestPurchase/storemock_config.yaml @@ -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 diff --git a/end-to-end/utils_test.go b/end-to-end/utils_test.go index 9d08de4ed..c493711db 100644 --- a/end-to-end/utils_test.go +++ b/end-to-end/utils_test.go @@ -18,7 +18,6 @@ import ( "github.com/canonical/ubuntu-pro-for-windows/common/wsltestutils" "github.com/stretchr/testify/require" "github.com/ubuntu/gowsl" - wsl "github.com/ubuntu/gowsl" ) func testSetup(t *testing.T) { @@ -152,7 +151,7 @@ func stopAgent(ctx context.Context) error { } //nolint:revive // testing.T must precede the context -func distroIsProAttached(t *testing.T, ctx context.Context, d wsl.Distro) (bool, error) { +func distroIsProAttached(t *testing.T, ctx context.Context, d gowsl.Distro) (bool, error) { t.Helper() out, err := d.Command(ctx, "pro status --format=json").Output() @@ -171,7 +170,7 @@ func distroIsProAttached(t *testing.T, ctx context.Context, d wsl.Distro) (bool, } //nolint:revive // testing.T must precede the context -func logWslProServiceJournal(t *testing.T, ctx context.Context, d wsl.Distro) { +func logWslProServiceJournal(t *testing.T, ctx context.Context, d gowsl.Distro) { t.Helper() out, err := d.Command(ctx, "journalctl -b --no-pager -u wsl-pro.service").CombinedOutput() diff --git a/gui/packages/p4w_ms_store/windows/CMakeLists.txt b/gui/packages/p4w_ms_store/windows/CMakeLists.txt index 5335858dd..29ed6face 100644 --- a/gui/packages/p4w_ms_store/windows/CMakeLists.txt +++ b/gui/packages/p4w_ms_store/windows/CMakeLists.txt @@ -72,10 +72,12 @@ set(STORE_API_SRC # API wrapper implementations "${STORE_API_DIR}/base/impl/StoreContext.cpp" "${STORE_API_DIR}/base/impl/StoreContext.hpp" + "${STORE_API_DIR}/base/impl/WinMockContext.cpp" + "${STORE_API_DIR}/base/impl/WinMockContext.hpp" ) if(DEFINED ENV{UP4W_TEST_WITH_MS_STORE_MOCK}) # TODO: Change to informative warning and update the text once mock client API wrappers are avaiable. - message(FATAL_ERROR "Unsupported build with the MS Store Mock client API due environment variable 'UP4W_TEST_WITH_MS_STORE_MOCK' set to '$ENV{UP4W_TEST_WITH_MS_STORE_MOCK}'.") + message(WARNING "Building the MS Store plugin with the mock client API due environment variable 'UP4W_TEST_WITH_MS_STORE_MOCK' set to '$ENV{UP4W_TEST_WITH_MS_STORE_MOCK}'.") list(APPEND STORE_API_DEFINES "UP4W_TEST_WITH_MS_STORE_MOCK") else() message(STATUS "Building with the production version of MS Store client API. Set the environment variable 'UP4W_TEST_WITH_MS_STORE_MOCK' if you want to build with the mock store API.") diff --git a/gui/packages/ubuntupro/end_to_end/end_to_end_test.dart b/gui/packages/ubuntupro/end_to_end/end_to_end_test.dart index e6feda9ec..9aa5f17d1 100644 --- a/gui/packages/ubuntupro/end_to_end/end_to_end_test.dart +++ b/gui/packages/ubuntupro/end_to_end/end_to_end_test.dart @@ -26,6 +26,7 @@ void main(List args) { const testCases = { 'TestOrganizationProvidedToken': testOrganizationProvidedToken, 'TestManualTokenInput': testManualTokenInput, + 'TestPurchase': testPurchase, }; final scenario = args[0]; @@ -87,3 +88,20 @@ Future testManualTokenInput(WidgetTester tester) async { l10n = tester.l10n(); expect(find.text(l10n.manuallyManaged), findsOneWidget); } + +Future testPurchase(WidgetTester tester) async { + await app.main(); + await tester.pumpAndSettle(); + + // The "subscribe now page" is only shown if the GUI communicates with the background agent. + var l10n = tester.l10n(); + final button = find.text(l10n.subscribeNow); + expect(button, findsOneWidget); + + await tester.tap(button); + await tester.pumpAndSettle(); + + // asserts that we transitioned to the store-managed status page. + l10n = tester.l10n(); + expect(find.text(l10n.storeManaged), findsOneWidget); +} diff --git a/mocks/storeserver/storemockserver/storemockserver.go b/mocks/storeserver/storemockserver/storemockserver.go index 247d18b1a..3705e4000 100644 --- a/mocks/storeserver/storemockserver/storemockserver.go +++ b/mocks/storeserver/storemockserver/storemockserver.go @@ -7,6 +7,7 @@ import ( "fmt" "log/slog" "net/http" + "sync" "time" "github.com/canonical/ubuntu-pro-for-windows/mocks/restserver" @@ -107,6 +108,8 @@ func (s Settings) Unmarshal(in []byte, unmarshaller func(in []byte, out interfac type Server struct { restserver.ServerBase settings Settings + + settingsMu sync.RWMutex } // Product models the interesting properties from the MS StoreProduct type. @@ -218,12 +221,18 @@ func (s *Server) handleGetProducts(w http.ResponseWriter, r *http.Request) { kinds := q[ProductKindsParam] ids := q[ProductIDsParam] var productsFound []Product + + // To avoid race with handlePurchase + s.settingsMu.RLock() + defer s.settingsMu.RUnlock() + for _, p := range s.settings.AllProducts { if slices.Contains(kinds, p.ProductKind) && slices.Contains(ids, p.StoreID) { productsFound = append(productsFound, p) } } + slog.Info(fmt.Sprintf("products found: %v", productsFound)) bs, err := json.Marshal(productsFound) if err != nil { w.WriteHeader(http.StatusInternalServerError) @@ -264,6 +273,9 @@ func (s *Server) handlePurchase(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") + s.settingsMu.Lock() + defer s.settingsMu.Unlock() + for i, p := range s.settings.AllProducts { if p.StoreID != id { continue @@ -276,6 +288,7 @@ func (s *Server) handlePurchase(w http.ResponseWriter, r *http.Request) { } year, month, day := time.Now().Date() + s.settings.AllProducts[i].ExpirationDate = time.Date(year+1, month, day, 1, 1, 1, 1, time.Local) // one year from now. s.settings.AllProducts[i].IsInUserCollection = true fmt.Fprintf(w, `{%q:%q}`, PurchaseStatusKey, SucceededResult) diff --git a/msix/agent/agent.targets b/msix/agent/agent.targets index 3a38588c7..f4f6c64bd 100644 --- a/msix/agent/agent.targets +++ b/msix/agent/agent.targets @@ -6,6 +6,7 @@ -ldflags -H=windowsgui + -tags=server_mocks @@ -32,7 +33,7 @@ - + diff --git a/msix/storeapi/storeapi.vcxproj b/msix/storeapi/storeapi.vcxproj index 8c063c59d..648f0564a 100644 --- a/msix/storeapi/storeapi.vcxproj +++ b/msix/storeapi/storeapi.vcxproj @@ -95,11 +95,13 @@ + + diff --git a/storeapi/go-wrapper/microsoftstore/store_windows.go b/storeapi/go-wrapper/microsoftstore/store_windows.go index c30fa0f11..7d98388e1 100644 --- a/storeapi/go-wrapper/microsoftstore/store_windows.go +++ b/storeapi/go-wrapper/microsoftstore/store_windows.go @@ -15,7 +15,7 @@ import ( const ( // TODO: Replace with real product ID. - productID = "ABCDEFG" + productID = "9P25B50XMKXT" ) var ( diff --git a/tools/build/build-appx.ps1 b/tools/build/build-appx.ps1 index b3cb3a62f..9d778fd29 100644 --- a/tools/build/build-appx.ps1 +++ b/tools/build/build-appx.ps1 @@ -4,7 +4,7 @@ #> param ( - [Parameter(Mandatory = $true, HelpMessage = "prodution, end_to_end_tests.")] + [Parameter(Mandatory = $true, HelpMessage = "production, end_to_end_tests.")] [string]$mode ) @@ -104,8 +104,8 @@ catch { Start-VsDevShell } -If ($mode -eq 'production' -and $null -ne $env:UP4W_TEST_WITH_MS_STORE_MOCK) { - Write-Warning "Building the app in Release mode with UP4W_TEST_WITH_MS_STORE_MOCK env var set may lead to build failure or surprising results. Value is $env:UP4W_TEST_WITH_MS_STORE_MOCK." +If ($mode -eq 'end_to_end_tests') { + $env:UP4W_TEST_WITH_MS_STORE_MOCK = 1 } msbuild.exe ` diff --git a/windows-agent/internal/contracts/contracts.go b/windows-agent/internal/contracts/contracts.go index 85e27162c..aa7794bd3 100644 --- a/windows-agent/internal/contracts/contracts.go +++ b/windows-agent/internal/contracts/contracts.go @@ -13,8 +13,6 @@ import ( "github.com/ubuntu/decorate" ) -const defaultProURL = "https://contracts.canonical.com" - type options struct { proURL *url.URL microsoftStore MicrosoftStore @@ -67,9 +65,9 @@ func ProToken(ctx context.Context, args ...Option) (token string, err error) { } if opts.proURL == nil { - url, err := url.Parse(defaultProURL) + url, err := defaultProBackendURL() if err != nil { - return "", fmt.Errorf("could not parse default contract server URL %q: %v", defaultProURL, err) + return "", fmt.Errorf("could not parse default contract server URL: %v", err) } opts.proURL = url } diff --git a/windows-agent/internal/contracts/default_url.go b/windows-agent/internal/contracts/default_url.go new file mode 100644 index 000000000..f0e44dae2 --- /dev/null +++ b/windows-agent/internal/contracts/default_url.go @@ -0,0 +1,11 @@ +//go:build !server_mocks + +package contracts + +import "net/url" + +const defaultProURL = "https://contracts.canonical.com" + +func defaultProBackendURL() (*url.URL, error) { + return url.Parse(defaultProURL) +} diff --git a/windows-agent/internal/contracts/default_url_mock.go b/windows-agent/internal/contracts/default_url_mock.go new file mode 100644 index 000000000..7908179d4 --- /dev/null +++ b/windows-agent/internal/contracts/default_url_mock.go @@ -0,0 +1,18 @@ +//go:build server_mocks + +package contracts + +import ( + "errors" + "fmt" + "net/url" + "os" +) + +func defaultProBackendURL() (*url.URL, error) { + endpoint := os.Getenv("UP4W_CONTRACTS_BACKEND_MOCK_ENDPOINT") + if len(endpoint) == 0 { + return nil, errors.New("Cannot read contracts backend mock endpoint from environment. Please set UP4W_CONTRACTS_BACKEND_MOCK_ENDPOINT.") + } + return url.Parse(fmt.Sprintf("http://%s", endpoint)) +}