diff --git a/windows-agent/internal/config/config_test.go b/windows-agent/internal/config/config_test.go index de53dea07..485d66d83 100644 --- a/windows-agent/internal/config/config_test.go +++ b/windows-agent/internal/config/config_test.go @@ -19,26 +19,34 @@ import ( "github.com/stretchr/testify/require" wsl "github.com/ubuntu/gowsl" wslmock "github.com/ubuntu/gowsl/mock" + "gopkg.in/yaml.v3" ) -// registryState represents how much data is in the registry. -type registryState uint64 +// settingsState represents how much data is in the registry. +type settingsState uint64 const ( - untouched registryState = 0x00 // Nothing UbuntuPro-related exists, as though the program had never ran before - keyExists registryState = 0x01 // Key exists but is empty - - orgTokenExists = keyExists | 1<<(iota+2) // Key exists, organization token field exists - userTokenExists // Key exists, user token field exists - storeTokenExists // Key exists, microsoft store token field exists - landscapeClientConfigExists // Key exists, landscape client config field exists - landscapeAgentUIDExists // Key exists, landscape agent UID field exists - - orgTokenHasValue = orgTokenExists | 1<<16 // Key exists, organization token field exists and is not empty - userTokenHasValue = userTokenExists | 1<<17 // Key exists, user token field exists and is not empty - storeTokenHasValue = storeTokenExists | 1<<18 // Key exists, microsoft store token field exists and is not empty - landscapeClientConfigHasValue = landscapeClientConfigExists | 1<<19 // Key exists, landscape client config field exists and is not empty - landscapeAgentUIDHasValue = landscapeAgentUIDExists | 1<<20 // Key exists, landscape agent UID field exists and is not empty + untouched settingsState = 0 // Nothing UbuntuPro-related exists, as though the program had never ran before + keyExists settingsState = 1 // Key exists but is empty + fileExists settingsState = 2 // File exists but is empty + + // Registry settings. + orgTokenExists = keyExists | 1<<3 // Key exists, organization token exists + orgLandscapeConfigExists = keyExists | 1<<4 // Key exists, organization landscape config exists + + orgTokenHasValue = orgTokenExists | 1<<5 // Key exists, organization token exists, and is not empty + orgLandscapeConfigHasValue = orgTokenExists | 1<<6 // Key exists, organization landscape config , and is not empty + + // File settings. + userTokenExists = fileExists | 1<<(7+iota) // File exists, user token exists + storeTokenExists // File exists, microsoft store token exists + userLandscapeConfigExists // File exists, landscape client config exists + landscapeUIDExists // File exists, landscape agent UID exists + + userTokenHasValue = userTokenExists | 1<<20 // File exists, user token exists, and is not empty + storeTokenHasValue = storeTokenExists | 1<<21 // File exists, microsoft store token exists, and is not empty + userLandscapeConfigHasValue = userLandscapeConfigExists | 1<<22 // File exists, landscape client config exists, and is not empty + landscapeUIDHasValue = landscapeUIDExists | 1<<23 // File exists, landscape agent UID exists, and is not empty ) func TestSubscription(t *testing.T) { @@ -46,27 +54,29 @@ func TestSubscription(t *testing.T) { testCases := map[string]struct { mockErrors uint32 - registryState registryState + breakFile bool + settingsState settingsState wantToken string wantSource config.Source wantError bool }{ - "Success": {registryState: userTokenHasValue, wantToken: "user_token", wantSource: config.SourceGUI}, - "Success when the key does not exist": {registryState: untouched}, - "Success when the key exists but is empty": {registryState: keyExists}, - "Success when the key exists but contains empty fields": {registryState: orgTokenExists | userTokenExists | storeTokenExists}, - - "Success when there is an organization token": {registryState: orgTokenHasValue, wantToken: "org_token", wantSource: config.SourceRegistry}, - "Success when there is a user token": {registryState: userTokenHasValue, wantToken: "user_token", wantSource: config.SourceGUI}, - "Success when there is a store token": {registryState: storeTokenHasValue, wantToken: "store_token", wantSource: config.SourceMicrosoftStore}, - - "Success when there are organization and user tokens": {registryState: orgTokenHasValue | userTokenHasValue, wantToken: "user_token", wantSource: config.SourceGUI}, - "Success when there are organization and store tokens": {registryState: orgTokenHasValue | storeTokenHasValue, wantToken: "store_token", wantSource: config.SourceMicrosoftStore}, - "Success when there are organization and user tokens, and an empty store token": {registryState: orgTokenHasValue | userTokenHasValue | storeTokenExists, wantToken: "user_token", wantSource: config.SourceGUI}, - - "Error when the registry key cannot be opened": {registryState: userTokenHasValue, mockErrors: registry.MockErrOnOpenKey, wantError: true}, - "Error when the registry key cannot be read from": {registryState: userTokenHasValue, mockErrors: registry.MockErrReadValue, wantError: true}, + "Success": {settingsState: userTokenHasValue, wantToken: "user_token", wantSource: config.SourceGUI}, + "Success when neither registry key nor conf file exist": {settingsState: untouched}, + "Success when the key exists but is empty": {settingsState: keyExists}, + "Success when the key exists but contains empty fields": {settingsState: orgTokenExists}, + + "Success when there is an organization token": {settingsState: orgTokenHasValue, wantToken: "org_token", wantSource: config.SourceRegistry}, + "Success when there is a user token": {settingsState: userTokenHasValue, wantToken: "user_token", wantSource: config.SourceGUI}, + "Success when there is a store token": {settingsState: storeTokenHasValue, wantToken: "store_token", wantSource: config.SourceMicrosoftStore}, + + "Success when there are organization and user tokens": {settingsState: orgTokenHasValue | userTokenHasValue, wantToken: "user_token", wantSource: config.SourceGUI}, + "Success when there are organization and store tokens": {settingsState: orgTokenHasValue | storeTokenHasValue, wantToken: "store_token", wantSource: config.SourceMicrosoftStore}, + "Success when there are organization and user tokens, and an empty store token": {settingsState: orgTokenHasValue | userTokenHasValue | storeTokenExists, wantToken: "user_token", wantSource: config.SourceGUI}, + + "Error when the registry key cannot be opened": {settingsState: orgTokenHasValue, mockErrors: registry.MockErrOnOpenKey, wantError: true}, + "Error when the registry key cannot be read from": {settingsState: orgTokenHasValue, mockErrors: registry.MockErrReadValue, wantError: true}, + "Error when the file cannot be read from": {settingsState: untouched, breakFile: true, wantError: true}, } for name, tc := range testCases { @@ -75,8 +85,8 @@ func TestSubscription(t *testing.T) { t.Parallel() ctx := context.Background() - r := setUpMockRegistry(tc.mockErrors, tc.registryState, false) - conf := config.New(ctx, config.WithRegistry(r)) + r, dir := setUpMockSettings(t, tc.mockErrors, tc.settingsState, false, tc.breakFile) + conf := config.New(ctx, dir, config.WithRegistry(r)) token, source, err := conf.Subscription(ctx) if tc.wantError { @@ -93,49 +103,74 @@ func TestSubscription(t *testing.T) { } } -func TestLandscapeClientConfig(t *testing.T) { +func TestLandscapeConfig(t *testing.T) { t.Parallel() - testConfigGetter(t, testConfigGetterSettings{ - getter: (*config.Config).LandscapeClientConfig, - getterName: "LandscapeClientConfig", - registryHasValue: landscapeClientConfigHasValue, - want: "[client]\nuser=JohnDoe", - }) -} + testCases := map[string]struct { + mockErrors uint32 + breakFile bool + settingsState settingsState -func TestLandscapeAgentUID(t *testing.T) { - t.Parallel() + wantLandscapeConfig string + wantSource config.Source + wantError bool + }{ + "Success": {settingsState: userLandscapeConfigHasValue, wantLandscapeConfig: "[client]\nuser=JohnDoe", wantSource: config.SourceGUI}, - testConfigGetter(t, testConfigGetterSettings{ - getter: (*config.Config).LandscapeAgentUID, - getterName: "LandscapeAgentUID", - registryHasValue: landscapeAgentUIDHasValue, - want: "landscapeUID1234", - }) -} + "Success when neither registry key nor conf file exist": {settingsState: untouched}, + "Success when the registry key exists but is empty": {settingsState: keyExists}, + "Success when the registry key exists but contains empty fields": {settingsState: orgLandscapeConfigExists}, + + "Success when there is an organization conf": {settingsState: orgLandscapeConfigHasValue, wantLandscapeConfig: "[client]\nuser=BigOrg", wantSource: config.SourceRegistry}, + "Success when there is a user conf": {settingsState: userLandscapeConfigHasValue, wantLandscapeConfig: "[client]\nuser=JohnDoe", wantSource: config.SourceGUI}, + + "Success when there are organization and user confs": {settingsState: orgLandscapeConfigHasValue | userLandscapeConfigHasValue, wantLandscapeConfig: "[client]\nuser=JohnDoe", wantSource: config.SourceGUI}, + + "Error when the registry key cannot be opened": {settingsState: orgTokenHasValue, mockErrors: registry.MockErrOnOpenKey, wantError: true}, + "Error when the registry key cannot be read from": {settingsState: orgTokenHasValue, mockErrors: registry.MockErrReadValue, wantError: true}, + "Error when the file cannot be read from": {settingsState: untouched, breakFile: true, wantError: true}, + } + + for name, tc := range testCases { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + ctx := context.Background() + + r, dir := setUpMockSettings(t, tc.mockErrors, tc.settingsState, false, tc.breakFile) + conf := config.New(ctx, dir, config.WithRegistry(r)) + + token, source, err := conf.LandscapeClientConfig(ctx) + if tc.wantError { + require.Error(t, err, "ProToken should return an error") + return + } + require.NoError(t, err, "ProToken should return no error") -type testConfigGetterSettings struct { - getter func(*config.Config, context.Context) (string, error) - getterName string - registryHasValue registryState - want string + // Test values + require.Equal(t, tc.wantLandscapeConfig, token, "Unexpected token value") + require.Equal(t, tc.wantSource, source, "Unexpected token source") + assert.Zero(t, r.OpenKeyCount.Load(), "Leaking keys after ProToken") + }) + } } -//nolint:thelper // This is the test itself, not a helper. Besides, a t.Helper() here would not affect the subtests. -func testConfigGetter(t *testing.T, s testConfigGetterSettings) { +func TestLandscapeAgentUID(t *testing.T) { + t.Parallel() + testCases := map[string]struct { - mockErrors uint32 - registryState registryState + settingsState settingsState + breakFile bool + breakFileContents bool wantError bool }{ - "Success": {registryState: s.registryHasValue}, - "Success when the key does not exist": {registryState: untouched}, - "Success when the value does not exist": {registryState: keyExists}, + "Success": {settingsState: landscapeUIDHasValue}, + "Success when the file does not exist": {settingsState: untouched}, + "Success when the value does not exist": {settingsState: fileExists}, - "Error when the registry key cannot be opened": {registryState: s.registryHasValue, mockErrors: registry.MockErrOnOpenKey, wantError: true}, - "Error when the registry key cannot be read from": {registryState: s.registryHasValue, mockErrors: registry.MockErrReadValue, wantError: true}, + "Error when the file cannot be opened": {settingsState: fileExists, breakFile: true, wantError: true}, + "Error when the file cannot be parsed": {settingsState: fileExists, breakFileContents: true, wantError: true}, } for name, tc := range testCases { @@ -144,25 +179,29 @@ func testConfigGetter(t *testing.T, s testConfigGetterSettings) { t.Parallel() ctx := context.Background() - r := setUpMockRegistry(tc.mockErrors, tc.registryState, false) - conf := config.New(ctx, config.WithRegistry(r)) + r, dir := setUpMockSettings(t, 0, tc.settingsState, false, tc.breakFile) + if tc.breakFileContents { + err := os.WriteFile(filepath.Join(dir, "config"), []byte("\tmessage:\n\t\tthis is not YAML!["), 0600) + require.NoError(t, err, "Setup: could not re-write config file") + } + conf := config.New(ctx, dir, config.WithRegistry(r)) - v, err := s.getter(conf, ctx) + v, err := conf.LandscapeAgentUID(ctx) if tc.wantError { - require.Error(t, err, "%s should return an error", s.getterName) + require.Error(t, err, "LandscapeAgentUID should return an error") return } - require.NoError(t, err, "%s should return no error", s.getterName) + require.NoError(t, err, "LandscapeAgentUID should return no error") // Test default values - if !tc.registryState.is(s.registryHasValue) { - require.Emptyf(t, v, "Unexpected value when %s is not set in registry", s.getterName) + if !tc.settingsState.is(landscapeUIDHasValue) { + require.Emptyf(t, v, "Unexpected value when LandscapeAgentUID is not set in registry") return } // Test non-default values - assert.Equalf(t, s.want, v, "%s returned an unexpected value", s.getterName) - assert.Zerof(t, r.OpenKeyCount.Load(), "Call to %s leaks registry keys", s.getterName) + assert.Equal(t, "landscapeUID1234", v, "LandscapeAgentUID returned an unexpected value") + assert.Zero(t, r.OpenKeyCount.Load(), "Call to LandscapeAgentUID leaks registry keys") }) } } @@ -172,21 +211,24 @@ func TestProvisioningTasks(t *testing.T) { testCases := map[string]struct { mockErrors uint32 - registryState registryState + settingsState settingsState + + wantToken string + wantLandscapeConf string + wantLandscapeUID string - want string wantNoLandscape bool wantError bool }{ - "Success when the key does not exist": {registryState: untouched}, - "Success when the pro token field does not exist": {registryState: keyExists}, - "Success when the pro token exists but is empty": {registryState: userTokenExists}, - "Success with a user token": {registryState: userTokenHasValue, want: "user_token"}, - "Success when there is Landscape config, but no UID": {registryState: landscapeClientConfigHasValue, wantNoLandscape: true}, - "Success when there is Landscape config and UID": {registryState: landscapeClientConfigHasValue | landscapeAgentUIDHasValue}, - - "Error when the registry key cannot be opened": {registryState: userTokenExists, mockErrors: registry.MockErrOnOpenKey, wantError: true}, - "Error when the registry key cannot be read from": {registryState: userTokenExists, mockErrors: registry.MockErrReadValue, wantError: true}, + "Success when the key does not exist": {settingsState: untouched}, + "Success when the pro token field does not exist": {settingsState: fileExists}, + "Success when the pro token exists but is empty": {settingsState: userTokenExists}, + "Success with a user token": {settingsState: userTokenHasValue, wantToken: "user_token"}, + "Success when there is Landscape config, but no UID": {settingsState: userLandscapeConfigHasValue, wantNoLandscape: true}, + "Success when there is Landscape config and UID": {settingsState: userLandscapeConfigHasValue | landscapeUIDHasValue, wantLandscapeConf: "[client]\nuser=JohnDoe", wantLandscapeUID: "landscapeUID1234"}, + + "Error when the registry key cannot be opened": {settingsState: orgTokenExists, mockErrors: registry.MockErrOnOpenKey, wantError: true}, + "Error when the registry key cannot be read from": {settingsState: orgTokenExists, mockErrors: registry.MockErrReadValue, wantError: true}, } for name, tc := range testCases { @@ -195,8 +237,8 @@ func TestProvisioningTasks(t *testing.T) { t.Parallel() ctx := context.Background() - r := setUpMockRegistry(tc.mockErrors, tc.registryState, false) - conf := config.New(ctx, config.WithRegistry(r)) + r, dir := setUpMockSettings(t, tc.mockErrors, tc.settingsState, false, false) + conf := config.New(ctx, dir, config.WithRegistry(r)) gotTasks, err := conf.ProvisioningTasks(ctx, "UBUNTU") if tc.wantError { @@ -206,13 +248,13 @@ func TestProvisioningTasks(t *testing.T) { require.NoError(t, err, "ProvisioningTasks should return no error") wantTasks := []task.Task{ - tasks.ProAttachment{Token: tc.want}, + tasks.ProAttachment{Token: tc.wantToken}, } if !tc.wantNoLandscape { wantTasks = append(wantTasks, tasks.LandscapeConfigure{ - Config: r.UbuntuProData["LandscapeClientConfig"], - HostagentUID: r.UbuntuProData["LandscapeAgentUID"], + Config: tc.wantLandscapeConf, + HostagentUID: tc.wantLandscapeUID, }) } @@ -226,24 +268,23 @@ func TestSetSubscription(t *testing.T) { testCases := map[string]struct { mockErrors uint32 - registryState registryState + settingsState settingsState accessIsReadOnly bool emptyToken bool - want string - wantError bool - wantErrorType error + want string + wantError bool }{ - "Success": {registryState: userTokenHasValue, want: "new_token"}, - "Success disabling a subscription": {registryState: userTokenHasValue, emptyToken: true, want: ""}, - "Success when the key does not exist": {registryState: untouched, want: "new_token"}, - "Success when the pro token field does not exist": {registryState: keyExists, want: "new_token"}, - "Success when there is a store token active": {registryState: storeTokenHasValue, want: "store_token"}, - - "Error when the registry key cannot be written on due to lack of permission": {registryState: userTokenHasValue, accessIsReadOnly: true, want: "user_token", wantError: true, wantErrorType: registry.ErrAccessDenied}, - "Error when the registry key cannot be opened": {registryState: userTokenHasValue, mockErrors: registry.MockErrOnCreateKey, want: "user_token", wantError: true, wantErrorType: registry.ErrMock}, - "Error when the registry key cannot be written on": {registryState: userTokenHasValue, mockErrors: registry.MockErrOnWriteValue, want: "user_token", wantError: true, wantErrorType: registry.ErrMock}, - "Error when the registry key cannot be read": {registryState: userTokenHasValue, mockErrors: registry.MockErrOnOpenKey, want: "user_token", wantError: true, wantErrorType: registry.ErrMock}, + "Success": {settingsState: userTokenHasValue, want: "new_token"}, + "Success disabling a subscription": {settingsState: userTokenHasValue, emptyToken: true, want: ""}, + "Success when the key does not exist": {settingsState: untouched, want: "new_token"}, + "Success when the pro token field does not exist": {settingsState: keyExists, want: "new_token"}, + "Success when there is a store token active": {settingsState: storeTokenHasValue, want: "store_token"}, + + "Error when the registry key cannot be written on due to lack of permission": {settingsState: userTokenHasValue, accessIsReadOnly: true, want: "user_token", wantError: true}, + "Error when the registry key cannot be opened": {settingsState: userTokenHasValue, mockErrors: registry.MockErrOnCreateKey, want: "user_token", wantError: true}, + "Error when the registry key cannot be written on": {settingsState: userTokenHasValue, mockErrors: registry.MockErrOnWriteValue, want: "user_token", wantError: true}, + "Error when the registry key cannot be read": {settingsState: userTokenHasValue, mockErrors: registry.MockErrOnOpenKey, want: "user_token", wantError: true}, } for name, tc := range testCases { @@ -252,8 +293,8 @@ func TestSetSubscription(t *testing.T) { t.Parallel() ctx := context.Background() - r := setUpMockRegistry(tc.mockErrors, tc.registryState, tc.accessIsReadOnly) - conf := config.New(ctx, config.WithRegistry(r)) + r, dir := setUpMockSettings(t, tc.mockErrors, tc.settingsState, tc.accessIsReadOnly, false) + conf := config.New(ctx, dir, config.WithRegistry(r)) token := "new_token" if tc.emptyToken { @@ -263,12 +304,9 @@ func TestSetSubscription(t *testing.T) { err := conf.SetSubscription(ctx, token, config.SourceGUI) if tc.wantError { require.Error(t, err, "SetSubscription should return an error") - if tc.wantErrorType != nil { - require.ErrorIs(t, err, tc.wantErrorType, "SetSubscription returned an error of unexpected type") - } - } else { - require.NoError(t, err, "SetSubscription should return no error") + return } + require.NoError(t, err, "SetSubscription should return no error") // Disable errors so we can retrieve the token r.Errors = 0 @@ -284,24 +322,19 @@ func TestSetLandscapeAgentUID(t *testing.T) { t.Parallel() testCases := map[string]struct { - mockErrors uint32 - registryState registryState - accessIsReadOnly bool - emptyUID bool + settingsState settingsState + emptyUID bool + breakFile bool - want string - wantError bool - wantErrorType error + want string + wantError bool }{ - "Success": {registryState: landscapeAgentUIDHasValue, want: "new_uid"}, - "Success unsetting the UID": {registryState: landscapeAgentUIDHasValue, emptyUID: true, want: ""}, - "Success when the key does not exist": {registryState: untouched, want: "new_uid"}, - "Success when the pro token field does not exist": {registryState: keyExists, want: "new_uid"}, + "Success overriding the UID": {settingsState: landscapeUIDHasValue, want: "new_uid"}, + "Success unsetting the UID": {settingsState: landscapeUIDHasValue, emptyUID: true, want: ""}, + "Success when the file does not exist": {settingsState: untouched, want: "new_uid"}, + "Success when the pro token field does not exist": {settingsState: fileExists, want: "new_uid"}, - "Error when the registry key cannot be written on due to lack of permission": {registryState: landscapeAgentUIDHasValue, accessIsReadOnly: true, want: "landscapeUID1234", wantError: true, wantErrorType: registry.ErrAccessDenied}, - "Error when the registry key cannot be opened": {registryState: landscapeAgentUIDHasValue, mockErrors: registry.MockErrOnCreateKey, want: "landscapeUID1234", wantError: true, wantErrorType: registry.ErrMock}, - "Error when the registry key cannot be written on": {registryState: landscapeAgentUIDHasValue, mockErrors: registry.MockErrOnWriteValue, want: "landscapeUID1234", wantError: true, wantErrorType: registry.ErrMock}, - "Error when the registry key cannot be read": {registryState: landscapeAgentUIDHasValue, mockErrors: registry.MockErrOnOpenKey, want: "landscapeUID1234", wantError: true, wantErrorType: registry.ErrMock}, + "Error when the file cannot be opened": {settingsState: landscapeUIDHasValue, breakFile: true, want: "landscapeUID1234", wantError: true}, } for name, tc := range testCases { @@ -310,8 +343,8 @@ func TestSetLandscapeAgentUID(t *testing.T) { t.Parallel() ctx := context.Background() - r := setUpMockRegistry(tc.mockErrors, tc.registryState, tc.accessIsReadOnly) - conf := config.New(ctx, config.WithRegistry(r)) + r, dir := setUpMockSettings(t, 0, tc.settingsState, false, tc.breakFile) + conf := config.New(ctx, dir, config.WithRegistry(r)) uid := "new_uid" if tc.emptyUID { @@ -321,12 +354,9 @@ func TestSetLandscapeAgentUID(t *testing.T) { err := conf.SetLandscapeAgentUID(ctx, uid) if tc.wantError { require.Error(t, err, "SetLandscapeAgentUID should return an error") - if tc.wantErrorType != nil { - require.ErrorIs(t, err, tc.wantErrorType, "SetLandscapeAgentUID returned an error of unexpected type") - } - } else { - require.NoError(t, err, "SetLandscapeAgentUID should return no error") + return } + require.NoError(t, err, "SetLandscapeAgentUID should return no error") // Disable errors so we can retrieve the UID r.Errors = 0 @@ -342,15 +372,15 @@ func TestIsReadOnly(t *testing.T) { t.Parallel() testCases := map[string]struct { - registryState registryState + settingsState settingsState readOnly bool registryErr bool want bool wantErr bool }{ - "Success when the registry can be written on": {registryState: keyExists, want: false}, - "Success when the registry cannot be written on": {registryState: keyExists, readOnly: true, want: true}, + "Success when the registry can be written on": {settingsState: keyExists, want: false}, + "Success when the registry cannot be written on": {settingsState: keyExists, readOnly: true, want: true}, "Success when the non-existent registry can be written on": {want: false}, "Success when the non-existent registry cannot be written on": {readOnly: true, want: true}, @@ -364,12 +394,12 @@ func TestIsReadOnly(t *testing.T) { t.Parallel() ctx := context.Background() - r := setUpMockRegistry(0, tc.registryState, tc.readOnly) + r, dir := setUpMockSettings(t, 0, tc.settingsState, tc.readOnly, false) if tc.registryErr { r.Errors = registry.MockErrOnCreateKey } - conf := config.New(ctx, config.WithRegistry(r)) + conf := config.New(ctx, dir, config.WithRegistry(r)) got, err := conf.IsReadOnly() if tc.wantErr { @@ -387,7 +417,7 @@ func TestFetchMicrosoftStoreSubscription(t *testing.T) { t.Parallel() testCases := map[string]struct { - registryState registryState + settingsState settingsState registryErr uint32 registryIsReadOnly bool @@ -395,7 +425,7 @@ func TestFetchMicrosoftStoreSubscription(t *testing.T) { wantErr bool }{ // TODO: Implement more test cases when the MS Store mock is available. There is no single successful test in here so far. - "Error when registry is read only": {registryState: userTokenHasValue, registryIsReadOnly: true, wantToken: "user_token", wantErr: true}, + "Error when registry is read only": {settingsState: userTokenHasValue, registryIsReadOnly: true, wantToken: "user_token", wantErr: true}, "Error when registry read-only check fails": {registryErr: registry.MockErrOnCreateKey, wantErr: true}, // Stub test-case: Must be replaced with Success/Error return values of contracts.ProToken @@ -410,8 +440,8 @@ func TestFetchMicrosoftStoreSubscription(t *testing.T) { ctx := context.Background() - r := setUpMockRegistry(tc.registryErr, tc.registryState, tc.registryIsReadOnly) - c := config.New(ctx, config.WithRegistry(r)) + r, dir := setUpMockSettings(t, tc.registryErr, tc.settingsState, tc.registryIsReadOnly, false) + c := config.New(ctx, dir, config.WithRegistry(r)) err := c.FetchMicrosoftStoreSubscription(ctx) if tc.wantErr { @@ -436,20 +466,20 @@ func TestUpdateRegistrySettings(t *testing.T) { testCases := map[string]struct { valueToChange string - registryState registryState + settingsState settingsState breakTaskfile bool wantTasks []string unwantedTasks []string wantErr bool }{ - "Success changing Pro token": {valueToChange: "ProTokenOrg", registryState: keyExists, wantTasks: []string{"tasks.ProAttachment"}, unwantedTasks: []string{"tasks.LandscapeConfigure"}}, - "Success changing Landscape without a UID": {valueToChange: "LandscapeClientConfig", registryState: keyExists, unwantedTasks: []string{"tasks.ProAttachment", "tasks.LandscapeConfigure"}}, - "Success changing Landscape with a UID": {valueToChange: "LandscapeClientConfig", registryState: landscapeAgentUIDHasValue, wantTasks: []string{"tasks.LandscapeConfigure"}, unwantedTasks: []string{"tasks.ProAttachment"}}, - "Success changing the Landscape UID": {valueToChange: "LandscapeAgentUID", registryState: keyExists, wantTasks: []string{"tasks.LandscapeConfigure"}, unwantedTasks: []string{"tasks.ProAttachment"}}, + "Success changing Pro token": {valueToChange: "UbuntuProToken", settingsState: keyExists, wantTasks: []string{"tasks.ProAttachment"}, unwantedTasks: []string{"tasks.LandscapeConfigure"}}, + "Success changing Landscape without a UID": {valueToChange: "LandscapeConfig", settingsState: keyExists, unwantedTasks: []string{"tasks.ProAttachment", "tasks.LandscapeConfigure"}}, + "Success changing Landscape with a UID": {valueToChange: "LandscapeConfig", settingsState: landscapeUIDHasValue, wantTasks: []string{"tasks.LandscapeConfigure"}, unwantedTasks: []string{"tasks.ProAttachment"}}, + "Success changing the Landscape UID": {valueToChange: "LandscapeUID", settingsState: keyExists, wantTasks: []string{"tasks.LandscapeConfigure"}, unwantedTasks: []string{"tasks.ProAttachment"}}, // Very implementation-detailed, but it's the only thing that actually triggers an error - "Error when the tasks cannot be submitted": {valueToChange: "ProTokenOrg", registryState: keyExists, breakTaskfile: true, wantErr: true}, + "Error when the tasks cannot be submitted": {valueToChange: "UbuntuProToken", settingsState: keyExists, breakTaskfile: true, wantErr: true}, } for name, tc := range testCases { @@ -472,20 +502,25 @@ func TestUpdateRegistrySettings(t *testing.T) { _, err = db.GetDistroAndUpdateProperties(ctx, distroName, distro.Properties{}) require.NoError(t, err, "Setup: could not add dummy distro to database") - r := setUpMockRegistry(0, tc.registryState, false) + r, dir := setUpMockSettings(t, 0, tc.settingsState, false, false) require.NoError(t, err, "Setup: could not create empty database") - c := config.New(ctx, config.WithRegistry(r)) + c := config.New(ctx, dir, config.WithRegistry(r)) - // Update value in registry - r.UbuntuProData[tc.valueToChange] = "NEW_VALUE!" + // Update value in registry or in config + if tc.valueToChange == "LandscapeUID" { + err := c.SetLandscapeAgentUID(ctx, "NEW_UID!") + require.NoError(t, err, "Setup: could not update Landscape UID") + } else { + r.UbuntuProData[tc.valueToChange] = "NEW_VALUE!" + } if tc.breakTaskfile { err := os.MkdirAll(taskFilePath, 0600) require.NoError(t, err, "could not create directory to interfere with task file") } - err = c.UpdateRegistrySettings(ctx, dir, db) + err = c.UpdateRegistrySettings(ctx, db) if tc.wantErr { require.Error(t, err, "UpdateRegistrySettings should return an error") return @@ -512,55 +547,90 @@ func readFileOrEmpty(path string) (string, error) { return string(out), err } -// is defines equality between flags. It is convenience function to check if a registryState matches a certain state. -func (state registryState) is(flag registryState) bool { +// is defines equality between flags. It is convenience function to check if a settingsState matches a certain state. +func (state settingsState) is(flag settingsState) bool { return state&flag == flag } -func setUpMockRegistry(mockErrors uint32, state registryState, readOnly bool) *registry.Mock { - r := registry.NewMock() +func setUpMockSettings(t *testing.T, mockErrors uint32, state settingsState, readOnly bool, fileBroken bool) (*registry.Mock, string) { + t.Helper() - r.Errors = mockErrors - r.KeyIsReadOnly = readOnly + // Mock registry + reg := registry.NewMock() + reg.Errors = mockErrors + reg.KeyIsReadOnly = readOnly if state.is(keyExists) { - r.KeyExists = true + reg.KeyExists = true } if state.is(orgTokenExists) { - r.UbuntuProData["ProTokenOrg"] = "" + reg.UbuntuProData["UbuntuProToken"] = "" } if state.is(orgTokenHasValue) { - r.UbuntuProData["ProTokenOrg"] = "org_token" + reg.UbuntuProData["UbuntuProToken"] = "org_token" + } + + if state.is(orgLandscapeConfigExists) { + reg.UbuntuProData["LandscapeConfig"] = "" + } + if state.is(orgLandscapeConfigHasValue) { + reg.UbuntuProData["LandscapeConfig"] = "[client]\nuser=BigOrg" + } + + // Mock file config + cacheDir := t.TempDir() + if fileBroken { + err := os.MkdirAll(filepath.Join(cacheDir, "config"), 0600) + require.NoError(t, err, "Setup: could not create directory to interfere with config") + return reg, cacheDir + } + + if !state.is(fileExists) { + return reg, cacheDir + } + + fileData := struct { + Landscape map[string]string + Subscription map[string]string + }{ + Subscription: make(map[string]string), + Landscape: make(map[string]string), } if state.is(userTokenExists) { - r.UbuntuProData["ProTokenUser"] = "" + fileData.Subscription["gui"] = "" } if state.is(userTokenHasValue) { - r.UbuntuProData["ProTokenUser"] = "user_token" + fileData.Subscription["gui"] = "user_token" } if state.is(storeTokenExists) { - r.UbuntuProData["ProTokenStore"] = "" + fileData.Subscription["store"] = "" } if state.is(storeTokenHasValue) { - r.UbuntuProData["ProTokenStore"] = "store_token" + fileData.Subscription["store"] = "store_token" } - if state.is(landscapeClientConfigExists) { - r.UbuntuProData["LandscapeClientConfig"] = "" + if state.is(userLandscapeConfigExists) { + fileData.Landscape["config"] = "" } - if state.is(landscapeClientConfigHasValue) { - r.UbuntuProData["LandscapeClientConfig"] = "[client]\nuser=JohnDoe" + if state.is(userLandscapeConfigHasValue) { + fileData.Landscape["config"] = "[client]\nuser=JohnDoe" } - if state.is(landscapeAgentUIDExists) { - r.UbuntuProData["LandscapeAgentUID"] = "" + if state.is(landscapeUIDExists) { + fileData.Landscape["uid"] = "" } - if state.is(landscapeAgentUIDHasValue) { - r.UbuntuProData["LandscapeAgentUID"] = "landscapeUID1234" + if state.is(landscapeUIDHasValue) { + fileData.Landscape["uid"] = "landscapeUID1234" } - return r + out, err := yaml.Marshal(fileData) + require.NoError(t, err, "Setup: could not marshal fake config") + + err = os.WriteFile(filepath.Join(cacheDir, "config"), out, 0600) + require.NoError(t, err, "Setup: could not write config file") + + return reg, cacheDir } diff --git a/windows-agent/internal/proservices/ui/ui_test.go b/windows-agent/internal/proservices/ui/ui_test.go index dceaa190f..fadc2b81a 100644 --- a/windows-agent/internal/proservices/ui/ui_test.go +++ b/windows-agent/internal/proservices/ui/ui_test.go @@ -3,6 +3,9 @@ package ui_test import ( "context" "errors" + "fmt" + "os" + "path/filepath" "testing" agentapi "github.com/canonical/ubuntu-pro-for-windows/agentapi/go" @@ -26,7 +29,7 @@ func TestNew(t *testing.T) { require.NoError(t, err, "Setup: empty database New() should return no error") defer db.Close(ctx) - conf := config.New(ctx, config.WithRegistry(registry.NewMock())) + conf := config.New(ctx, dir, config.WithRegistry(registry.NewMock())) _ = ui.New(context.Background(), conf, db) } @@ -80,9 +83,12 @@ func TestAttachPro(t *testing.T) { m := registry.NewMock() m.KeyIsReadOnly = tc.registryReadOnly m.KeyExists = true - m.UbuntuProData["ProTokenUser"] = originalToken - conf := config.New(ctx, config.WithRegistry(m)) + contents := fmt.Sprintf("subscription:\n gui: %s", originalToken) + err = os.WriteFile(filepath.Join(dir, "config"), []byte(contents), 0600) + require.NoError(t, err, "Setup: could not write config file") + + conf := config.New(ctx, dir, config.WithRegistry(m)) serv := ui.New(context.Background(), conf, db) info := agentapi.ProAttachInfo{Token: tc.token}