diff --git a/management/server/http/testing/integration/accounts_handler_integration_test.go b/management/server/http/testing/integration/accounts_handler_integration_test.go new file mode 100644 index 000000000..511730ee5 --- /dev/null +++ b/management/server/http/testing/integration/accounts_handler_integration_test.go @@ -0,0 +1,238 @@ +//go:build integration + +package integration + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/netbirdio/netbird/management/server/http/testing/testing_tools" + "github.com/netbirdio/netbird/management/server/http/testing/testing_tools/channel" + "github.com/netbirdio/netbird/management/server/types" + "github.com/netbirdio/netbird/shared/management/http/api" +) + +func Test_Accounts_GetAll(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, true}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + for _, user := range users { + t.Run(user.name+" - Get all accounts", func(t *testing.T) { + apiHandler, _, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/accounts.sql", nil, true) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, "/api/accounts", user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, http.StatusOK, user.expectResponse) + if !expectResponse { + return + } + + got := []api.Account{} + if err := json.Unmarshal(content, &got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + + assert.Equal(t, 1, len(got)) + account := got[0] + assert.Equal(t, "test.com", account.Domain) + assert.Equal(t, "private", account.DomainCategory) + assert.Equal(t, true, account.Settings.PeerLoginExpirationEnabled) + assert.Equal(t, 86400, account.Settings.PeerLoginExpiration) + assert.Equal(t, false, account.Settings.RegularUsersViewBlocked) + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } +} + +func Test_Accounts_Update(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + trueVal := true + falseVal := false + + tt := []struct { + name string + expectedStatus int + requestBody *api.AccountRequest + verifyResponse func(t *testing.T, account *api.Account) + verifyDB func(t *testing.T, account *types.Account) + }{ + { + name: "Disable peer login expiration", + requestBody: &api.AccountRequest{ + Settings: api.AccountSettings{ + PeerLoginExpirationEnabled: false, + PeerLoginExpiration: 86400, + }, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, account *api.Account) { + t.Helper() + assert.Equal(t, false, account.Settings.PeerLoginExpirationEnabled) + }, + verifyDB: func(t *testing.T, dbAccount *types.Account) { + t.Helper() + assert.Equal(t, false, dbAccount.Settings.PeerLoginExpirationEnabled) + }, + }, + { + name: "Update peer login expiration to 48h", + requestBody: &api.AccountRequest{ + Settings: api.AccountSettings{ + PeerLoginExpirationEnabled: true, + PeerLoginExpiration: 172800, + }, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, account *api.Account) { + t.Helper() + assert.Equal(t, 172800, account.Settings.PeerLoginExpiration) + }, + verifyDB: func(t *testing.T, dbAccount *types.Account) { + t.Helper() + assert.Equal(t, 172800*time.Second, dbAccount.Settings.PeerLoginExpiration) + }, + }, + { + name: "Enable regular users view blocked", + requestBody: &api.AccountRequest{ + Settings: api.AccountSettings{ + PeerLoginExpirationEnabled: true, + PeerLoginExpiration: 86400, + RegularUsersViewBlocked: true, + }, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, account *api.Account) { + t.Helper() + assert.Equal(t, true, account.Settings.RegularUsersViewBlocked) + }, + verifyDB: func(t *testing.T, dbAccount *types.Account) { + t.Helper() + assert.Equal(t, true, dbAccount.Settings.RegularUsersViewBlocked) + }, + }, + { + name: "Enable groups propagation", + requestBody: &api.AccountRequest{ + Settings: api.AccountSettings{ + PeerLoginExpirationEnabled: true, + PeerLoginExpiration: 86400, + GroupsPropagationEnabled: &trueVal, + }, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, account *api.Account) { + t.Helper() + assert.NotNil(t, account.Settings.GroupsPropagationEnabled) + assert.Equal(t, true, *account.Settings.GroupsPropagationEnabled) + }, + verifyDB: func(t *testing.T, dbAccount *types.Account) { + t.Helper() + assert.Equal(t, true, dbAccount.Settings.GroupsPropagationEnabled) + }, + }, + { + name: "Enable JWT groups", + requestBody: &api.AccountRequest{ + Settings: api.AccountSettings{ + PeerLoginExpirationEnabled: true, + PeerLoginExpiration: 86400, + GroupsPropagationEnabled: &falseVal, + JwtGroupsEnabled: &trueVal, + JwtGroupsClaimName: stringPointer("groups"), + }, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, account *api.Account) { + t.Helper() + assert.NotNil(t, account.Settings.JwtGroupsEnabled) + assert.Equal(t, true, *account.Settings.JwtGroupsEnabled) + assert.NotNil(t, account.Settings.JwtGroupsClaimName) + assert.Equal(t, "groups", *account.Settings.JwtGroupsClaimName) + }, + verifyDB: func(t *testing.T, dbAccount *types.Account) { + t.Helper() + assert.Equal(t, true, dbAccount.Settings.JWTGroupsEnabled) + assert.Equal(t, "groups", dbAccount.Settings.JWTGroupsClaimName) + }, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, am, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/accounts.sql", nil, false) + + body, err := json.Marshal(tc.requestBody) + if err != nil { + t.Fatalf("Failed to marshal request body: %v", err) + } + + req := testing_tools.BuildRequest(t, body, http.MethodPut, strings.Replace("/api/accounts/{accountId}", "{accountId}", testing_tools.TestAccountId, 1), user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + got := &api.Account{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + + assert.Equal(t, testing_tools.TestAccountId, got.Id) + assert.Equal(t, "test.com", got.Domain) + tc.verifyResponse(t, got) + + db := testing_tools.GetDB(t, am.GetStore()) + dbAccount := testing_tools.VerifyAccountSettings(t, db) + tc.verifyDB(t, dbAccount) + }) + } + } +} + +func stringPointer(s string) *string { + return &s +} diff --git a/management/server/http/testing/integration/dns_handler_integration_test.go b/management/server/http/testing/integration/dns_handler_integration_test.go new file mode 100644 index 000000000..7ada5e462 --- /dev/null +++ b/management/server/http/testing/integration/dns_handler_integration_test.go @@ -0,0 +1,554 @@ +//go:build integration + +package integration + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/netbirdio/netbird/management/server/http/testing/testing_tools" + "github.com/netbirdio/netbird/management/server/http/testing/testing_tools/channel" + "github.com/netbirdio/netbird/shared/management/http/api" +) + +func Test_Nameservers_GetAll(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + for _, user := range users { + t.Run(user.name+" - Get all nameservers", func(t *testing.T) { + apiHandler, _, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/dns.sql", nil, true) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, "/api/dns/nameservers", user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, http.StatusOK, user.expectResponse) + if !expectResponse { + return + } + + got := []api.NameserverGroup{} + if err := json.Unmarshal(content, &got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + + assert.Equal(t, 1, len(got)) + assert.Equal(t, "testNSGroup", got[0].Name) + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } +} + +func Test_Nameservers_GetById(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + tt := []struct { + name string + nsGroupId string + expectedStatus int + expectGroup bool + }{ + { + name: "Get existing nameserver group", + nsGroupId: "testNSGroupId", + expectedStatus: http.StatusOK, + expectGroup: true, + }, + { + name: "Get non-existing nameserver group", + nsGroupId: "nonExistingNSGroupId", + expectedStatus: http.StatusNotFound, + expectGroup: false, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, _, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/dns.sql", nil, true) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, strings.Replace("/api/dns/nameservers/{nsgroupId}", "{nsgroupId}", tc.nsGroupId, 1), user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.expectGroup { + got := &api.NameserverGroup{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + assert.Equal(t, "testNSGroupId", got.Id) + assert.Equal(t, "testNSGroup", got.Name) + } + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } + } +} + +func Test_Nameservers_Create(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + tt := []struct { + name string + requestBody *api.PostApiDnsNameserversJSONRequestBody + expectedStatus int + verifyResponse func(t *testing.T, nsGroup *api.NameserverGroup) + }{ + { + name: "Create nameserver group with single NS", + requestBody: &api.PostApiDnsNameserversJSONRequestBody{ + Name: "newNSGroup", + Description: "a new nameserver group", + Nameservers: []api.Nameserver{ + {Ip: "8.8.8.8", NsType: "udp", Port: 53}, + }, + Groups: []string{testing_tools.TestGroupId}, + Primary: false, + Domains: []string{"test.com"}, + Enabled: true, + SearchDomainsEnabled: false, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, nsGroup *api.NameserverGroup) { + t.Helper() + assert.NotEmpty(t, nsGroup.Id) + assert.Equal(t, "newNSGroup", nsGroup.Name) + assert.Equal(t, 1, len(nsGroup.Nameservers)) + assert.Equal(t, false, nsGroup.Primary) + }, + }, + { + name: "Create primary nameserver group", + requestBody: &api.PostApiDnsNameserversJSONRequestBody{ + Name: "primaryNS", + Description: "primary nameserver", + Nameservers: []api.Nameserver{ + {Ip: "1.1.1.1", NsType: "udp", Port: 53}, + }, + Groups: []string{testing_tools.TestGroupId}, + Primary: true, + Domains: []string{}, + Enabled: true, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, nsGroup *api.NameserverGroup) { + t.Helper() + assert.Equal(t, true, nsGroup.Primary) + }, + }, + { + name: "Create nameserver group with empty groups", + requestBody: &api.PostApiDnsNameserversJSONRequestBody{ + Name: "emptyGroupsNS", + Description: "no groups", + Nameservers: []api.Nameserver{ + {Ip: "8.8.8.8", NsType: "udp", Port: 53}, + }, + Groups: []string{}, + Primary: true, + Domains: []string{}, + Enabled: true, + }, + expectedStatus: http.StatusUnprocessableEntity, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, am, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/dns.sql", nil, false) + + body, err := json.Marshal(tc.requestBody) + if err != nil { + t.Fatalf("Failed to marshal request body: %v", err) + } + + req := testing_tools.BuildRequest(t, body, http.MethodPost, "/api/dns/nameservers", user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.verifyResponse != nil { + got := &api.NameserverGroup{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + tc.verifyResponse(t, got) + + // Verify the created NS group directly in the DB + db := testing_tools.GetDB(t, am.GetStore()) + dbNS := testing_tools.VerifyNSGroupInDB(t, db, got.Id) + assert.Equal(t, got.Name, dbNS.Name) + assert.Equal(t, got.Primary, dbNS.Primary) + assert.Equal(t, len(got.Nameservers), len(dbNS.NameServers)) + assert.Equal(t, got.Enabled, dbNS.Enabled) + assert.Equal(t, got.SearchDomainsEnabled, dbNS.SearchDomainsEnabled) + } + }) + } + } +} + +func Test_Nameservers_Update(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + tt := []struct { + name string + nsGroupId string + requestBody *api.PutApiDnsNameserversNsgroupIdJSONRequestBody + expectedStatus int + verifyResponse func(t *testing.T, nsGroup *api.NameserverGroup) + }{ + { + name: "Update nameserver group name", + nsGroupId: "testNSGroupId", + requestBody: &api.PutApiDnsNameserversNsgroupIdJSONRequestBody{ + Name: "updatedNSGroup", + Description: "updated description", + Nameservers: []api.Nameserver{ + {Ip: "1.1.1.1", NsType: "udp", Port: 53}, + }, + Groups: []string{testing_tools.TestGroupId}, + Primary: false, + Domains: []string{"example.com"}, + Enabled: true, + SearchDomainsEnabled: false, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, nsGroup *api.NameserverGroup) { + t.Helper() + assert.Equal(t, "updatedNSGroup", nsGroup.Name) + assert.Equal(t, "updated description", nsGroup.Description) + }, + }, + { + name: "Update non-existing nameserver group", + nsGroupId: "nonExistingNSGroupId", + requestBody: &api.PutApiDnsNameserversNsgroupIdJSONRequestBody{ + Name: "whatever", + Nameservers: []api.Nameserver{ + {Ip: "1.1.1.1", NsType: "udp", Port: 53}, + }, + Groups: []string{testing_tools.TestGroupId}, + Primary: true, + Domains: []string{}, + Enabled: true, + }, + expectedStatus: http.StatusNotFound, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, am, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/dns.sql", nil, false) + + body, err := json.Marshal(tc.requestBody) + if err != nil { + t.Fatalf("Failed to marshal request body: %v", err) + } + + req := testing_tools.BuildRequest(t, body, http.MethodPut, strings.Replace("/api/dns/nameservers/{nsgroupId}", "{nsgroupId}", tc.nsGroupId, 1), user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.verifyResponse != nil { + got := &api.NameserverGroup{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + tc.verifyResponse(t, got) + + // Verify the updated NS group directly in the DB + db := testing_tools.GetDB(t, am.GetStore()) + dbNS := testing_tools.VerifyNSGroupInDB(t, db, tc.nsGroupId) + assert.Equal(t, "updatedNSGroup", dbNS.Name) + assert.Equal(t, "updated description", dbNS.Description) + assert.Equal(t, false, dbNS.Primary) + assert.Equal(t, true, dbNS.Enabled) + assert.Equal(t, 1, len(dbNS.NameServers)) + assert.Equal(t, false, dbNS.SearchDomainsEnabled) + } + }) + } + } +} + +func Test_Nameservers_Delete(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + tt := []struct { + name string + nsGroupId string + expectedStatus int + }{ + { + name: "Delete existing nameserver group", + nsGroupId: "testNSGroupId", + expectedStatus: http.StatusOK, + }, + { + name: "Delete non-existing nameserver group", + nsGroupId: "nonExistingNSGroupId", + expectedStatus: http.StatusNotFound, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, am, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/dns.sql", nil, false) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodDelete, strings.Replace("/api/dns/nameservers/{nsgroupId}", "{nsgroupId}", tc.nsGroupId, 1), user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + + // Verify deletion in DB for successful deletes by privileged users + if tc.expectedStatus == http.StatusOK && user.expectResponse { + db := testing_tools.GetDB(t, am.GetStore()) + testing_tools.VerifyNSGroupNotInDB(t, db, tc.nsGroupId) + } + }) + } + } +} + +func Test_DnsSettings_Get(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + for _, user := range users { + t.Run(user.name+" - Get DNS settings", func(t *testing.T) { + apiHandler, _, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/dns.sql", nil, true) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, "/api/dns/settings", user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, http.StatusOK, user.expectResponse) + if !expectResponse { + return + } + + got := &api.DNSSettings{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + + assert.NotNil(t, got.DisabledManagementGroups) + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } +} + +func Test_DnsSettings_Update(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + tt := []struct { + name string + requestBody *api.PutApiDnsSettingsJSONRequestBody + expectedStatus int + verifyResponse func(t *testing.T, settings *api.DNSSettings) + expectedDBDisabledMgmtLen int + expectedDBDisabledMgmtItem string + }{ + { + name: "Update disabled management groups", + requestBody: &api.PutApiDnsSettingsJSONRequestBody{ + DisabledManagementGroups: []string{testing_tools.TestGroupId}, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, settings *api.DNSSettings) { + t.Helper() + assert.Equal(t, 1, len(settings.DisabledManagementGroups)) + assert.Equal(t, testing_tools.TestGroupId, settings.DisabledManagementGroups[0]) + }, + expectedDBDisabledMgmtLen: 1, + expectedDBDisabledMgmtItem: testing_tools.TestGroupId, + }, + { + name: "Update with empty disabled management groups", + requestBody: &api.PutApiDnsSettingsJSONRequestBody{ + DisabledManagementGroups: []string{}, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, settings *api.DNSSettings) { + t.Helper() + assert.Equal(t, 0, len(settings.DisabledManagementGroups)) + }, + expectedDBDisabledMgmtLen: 0, + }, + { + name: "Update with non-existing group", + requestBody: &api.PutApiDnsSettingsJSONRequestBody{ + DisabledManagementGroups: []string{"nonExistingGroupId"}, + }, + expectedStatus: http.StatusUnprocessableEntity, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, am, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/dns.sql", nil, false) + + body, err := json.Marshal(tc.requestBody) + if err != nil { + t.Fatalf("Failed to marshal request body: %v", err) + } + + req := testing_tools.BuildRequest(t, body, http.MethodPut, "/api/dns/settings", user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.verifyResponse != nil { + got := &api.DNSSettings{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + tc.verifyResponse(t, got) + + // Verify DNS settings directly in the DB + db := testing_tools.GetDB(t, am.GetStore()) + dbAccount := testing_tools.VerifyAccountSettings(t, db) + assert.Equal(t, tc.expectedDBDisabledMgmtLen, len(dbAccount.DNSSettings.DisabledManagementGroups)) + if tc.expectedDBDisabledMgmtItem != "" { + assert.Contains(t, dbAccount.DNSSettings.DisabledManagementGroups, tc.expectedDBDisabledMgmtItem) + } + } + }) + } + } +} diff --git a/management/server/http/testing/integration/events_handler_integration_test.go b/management/server/http/testing/integration/events_handler_integration_test.go new file mode 100644 index 000000000..6611b60ee --- /dev/null +++ b/management/server/http/testing/integration/events_handler_integration_test.go @@ -0,0 +1,105 @@ +//go:build integration + +package integration + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/netbirdio/netbird/management/server/http/testing/testing_tools" + "github.com/netbirdio/netbird/management/server/http/testing/testing_tools/channel" + "github.com/netbirdio/netbird/shared/management/http/api" +) + +func Test_Events_GetAll(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + for _, user := range users { + t.Run(user.name+" - Get all events", func(t *testing.T) { + apiHandler, _, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/events.sql", nil, false) + + // First, perform a mutation to generate an event (create a group as admin) + groupBody, err := json.Marshal(&api.GroupRequest{Name: "eventTestGroup"}) + if err != nil { + t.Fatalf("Failed to marshal group request: %v", err) + } + createReq := testing_tools.BuildRequest(t, groupBody, http.MethodPost, "/api/groups", testing_tools.TestAdminId) + createRecorder := httptest.NewRecorder() + apiHandler.ServeHTTP(createRecorder, createReq) + assert.Equal(t, http.StatusOK, createRecorder.Code, "Failed to create group to generate event") + + // Now query events + req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, "/api/events", user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, http.StatusOK, user.expectResponse) + if !expectResponse { + return + } + + got := []api.Event{} + if err := json.Unmarshal(content, &got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + + assert.GreaterOrEqual(t, len(got), 1, "Expected at least one event after creating a group") + + // Verify the group creation event exists + found := false + for _, event := range got { + if event.ActivityCode == "group.add" { + found = true + assert.Equal(t, testing_tools.TestAdminId, event.InitiatorId) + assert.Equal(t, "Group created", event.Activity) + break + } + } + assert.True(t, found, "Expected to find a group.add event") + }) + } +} + +func Test_Events_GetAll_Empty(t *testing.T) { + apiHandler, _, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/events.sql", nil, true) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, "/api/events", testing_tools.TestAdminId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, http.StatusOK, true) + if !expectResponse { + return + } + + got := []api.Event{} + if err := json.Unmarshal(content, &got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + + assert.Equal(t, 0, len(got), "Expected empty events list when no mutations have been performed") + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } +} diff --git a/management/server/http/testing/integration/groups_handler_integration_test.go b/management/server/http/testing/integration/groups_handler_integration_test.go new file mode 100644 index 000000000..edb43f3f3 --- /dev/null +++ b/management/server/http/testing/integration/groups_handler_integration_test.go @@ -0,0 +1,382 @@ +//go:build integration + +package integration + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/netbirdio/netbird/management/server/http/testing/testing_tools" + "github.com/netbirdio/netbird/management/server/http/testing/testing_tools/channel" + "github.com/netbirdio/netbird/shared/management/http/api" +) + +func Test_Groups_GetAll(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, true}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + for _, user := range users { + t.Run(user.name+" - Get all groups", func(t *testing.T) { + apiHandler, _, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/groups.sql", nil, true) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, "/api/groups", user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, http.StatusOK, user.expectResponse) + if !expectResponse { + return + } + + got := []api.Group{} + if err := json.Unmarshal(content, &got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + + assert.GreaterOrEqual(t, len(got), 2) + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } +} + +func Test_Groups_GetById(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, true}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + tt := []struct { + name string + groupId string + expectedStatus int + expectGroup bool + }{ + { + name: "Get existing group", + groupId: testing_tools.TestGroupId, + expectedStatus: http.StatusOK, + expectGroup: true, + }, + { + name: "Get non-existing group", + groupId: "nonExistingGroupId", + expectedStatus: http.StatusNotFound, + expectGroup: false, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, _, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/groups.sql", nil, true) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, strings.Replace("/api/groups/{groupId}", "{groupId}", tc.groupId, 1), user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.expectGroup { + got := &api.Group{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + assert.Equal(t, tc.groupId, got.Id) + assert.Equal(t, "testGroupName", got.Name) + assert.Equal(t, 1, got.PeersCount) + } + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } + } +} + +func Test_Groups_Create(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + tt := []struct { + name string + requestBody *api.GroupRequest + expectedStatus int + verifyResponse func(t *testing.T, group *api.Group) + }{ + { + name: "Create group with valid name", + requestBody: &api.GroupRequest{ + Name: "brandNewGroup", + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, group *api.Group) { + t.Helper() + assert.NotEmpty(t, group.Id) + assert.Equal(t, "brandNewGroup", group.Name) + assert.Equal(t, 0, group.PeersCount) + }, + }, + { + name: "Create group with peers", + requestBody: &api.GroupRequest{ + Name: "groupWithPeers", + Peers: &[]string{testing_tools.TestPeerId}, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, group *api.Group) { + t.Helper() + assert.NotEmpty(t, group.Id) + assert.Equal(t, "groupWithPeers", group.Name) + assert.Equal(t, 1, group.PeersCount) + }, + }, + { + name: "Create group with empty name", + requestBody: &api.GroupRequest{ + Name: "", + }, + expectedStatus: http.StatusUnprocessableEntity, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, am, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/groups.sql", nil, false) + + body, err := json.Marshal(tc.requestBody) + if err != nil { + t.Fatalf("Failed to marshal request body: %v", err) + } + + req := testing_tools.BuildRequest(t, body, http.MethodPost, "/api/groups", user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.verifyResponse != nil { + got := &api.Group{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + tc.verifyResponse(t, got) + + // Verify group exists in DB + db := testing_tools.GetDB(t, am.GetStore()) + dbGroup := testing_tools.VerifyGroupInDB(t, db, got.Id) + assert.Equal(t, tc.requestBody.Name, dbGroup.Name) + } + }) + } + } +} + +func Test_Groups_Update(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + tt := []struct { + name string + groupId string + requestBody *api.GroupRequest + expectedStatus int + verifyResponse func(t *testing.T, group *api.Group) + }{ + { + name: "Update group name", + groupId: testing_tools.TestGroupId, + requestBody: &api.GroupRequest{ + Name: "updatedGroupName", + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, group *api.Group) { + t.Helper() + assert.Equal(t, testing_tools.TestGroupId, group.Id) + assert.Equal(t, "updatedGroupName", group.Name) + }, + }, + { + name: "Update group peers", + groupId: testing_tools.TestGroupId, + requestBody: &api.GroupRequest{ + Name: "testGroupName", + Peers: &[]string{}, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, group *api.Group) { + t.Helper() + assert.Equal(t, 0, group.PeersCount) + }, + }, + { + name: "Update with empty name", + groupId: testing_tools.TestGroupId, + requestBody: &api.GroupRequest{ + Name: "", + }, + expectedStatus: http.StatusUnprocessableEntity, + }, + { + name: "Update non-existing group", + groupId: "nonExistingGroupId", + requestBody: &api.GroupRequest{ + Name: "someName", + }, + expectedStatus: http.StatusNotFound, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, am, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/groups.sql", nil, false) + + body, err := json.Marshal(tc.requestBody) + if err != nil { + t.Fatalf("Failed to marshal request body: %v", err) + } + + req := testing_tools.BuildRequest(t, body, http.MethodPut, strings.Replace("/api/groups/{groupId}", "{groupId}", tc.groupId, 1), user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.verifyResponse != nil { + got := &api.Group{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + tc.verifyResponse(t, got) + + // Verify updated group in DB + db := testing_tools.GetDB(t, am.GetStore()) + dbGroup := testing_tools.VerifyGroupInDB(t, db, tc.groupId) + assert.Equal(t, tc.requestBody.Name, dbGroup.Name) + } + }) + } + } +} + +func Test_Groups_Delete(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + tt := []struct { + name string + groupId string + expectedStatus int + }{ + { + name: "Delete existing group not in use", + groupId: testing_tools.NewGroupId, + expectedStatus: http.StatusOK, + }, + { + name: "Delete non-existing group", + groupId: "nonExistingGroupId", + expectedStatus: http.StatusBadRequest, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, am, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/groups.sql", nil, false) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodDelete, strings.Replace("/api/groups/{groupId}", "{groupId}", tc.groupId, 1), user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + _, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if expectResponse && tc.expectedStatus == http.StatusOK { + db := testing_tools.GetDB(t, am.GetStore()) + testing_tools.VerifyGroupNotInDB(t, db, tc.groupId) + } + }) + } + } +} diff --git a/management/server/http/testing/integration/networks_handler_integration_test.go b/management/server/http/testing/integration/networks_handler_integration_test.go new file mode 100644 index 000000000..4cb6b268b --- /dev/null +++ b/management/server/http/testing/integration/networks_handler_integration_test.go @@ -0,0 +1,1434 @@ +//go:build integration + +package integration + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/netbirdio/netbird/management/server/http/testing/testing_tools" + "github.com/netbirdio/netbird/management/server/http/testing/testing_tools/channel" + "github.com/netbirdio/netbird/shared/management/http/api" +) + +func Test_Networks_GetAll(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + for _, user := range users { + t.Run(user.name+" - Get all networks", func(t *testing.T) { + apiHandler, _, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/networks.sql", nil, true) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, "/api/networks", user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, http.StatusOK, user.expectResponse) + if !expectResponse { + return + } + + got := []*api.Network{} + if err := json.Unmarshal(content, &got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + + assert.Equal(t, 1, len(got)) + assert.Equal(t, "testNetworkId", got[0].Id) + assert.Equal(t, "testNetwork", got[0].Name) + assert.Equal(t, "test network description", *got[0].Description) + assert.GreaterOrEqual(t, len(got[0].Routers), 1) + assert.GreaterOrEqual(t, len(got[0].Resources), 1) + assert.GreaterOrEqual(t, got[0].RoutingPeersCount, 1) + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } +} + +func Test_Networks_GetById(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + tt := []struct { + name string + networkId string + expectedStatus int + expectNetwork bool + }{ + { + name: "Get existing network", + networkId: "testNetworkId", + expectedStatus: http.StatusOK, + expectNetwork: true, + }, + { + name: "Get non-existing network", + networkId: "nonExistingNetworkId", + expectedStatus: http.StatusNotFound, + expectNetwork: false, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, _, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/networks.sql", nil, true) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, strings.Replace("/api/networks/{networkId}", "{networkId}", tc.networkId, 1), user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.expectNetwork { + got := &api.Network{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + assert.Equal(t, tc.networkId, got.Id) + assert.Equal(t, "testNetwork", got.Name) + assert.Equal(t, "test network description", *got.Description) + assert.GreaterOrEqual(t, len(got.Routers), 1) + assert.GreaterOrEqual(t, len(got.Resources), 1) + } + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } + } +} + +func Test_Networks_Create(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + desc := "new network description" + + tt := []struct { + name string + requestBody *api.NetworkRequest + expectedStatus int + verifyResponse func(t *testing.T, network *api.Network) + }{ + { + name: "Create network with name and description", + requestBody: &api.NetworkRequest{ + Name: "newNetwork", + Description: &desc, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, network *api.Network) { + t.Helper() + assert.NotEmpty(t, network.Id) + assert.Equal(t, "newNetwork", network.Name) + assert.Equal(t, "new network description", *network.Description) + assert.Empty(t, network.Routers) + assert.Empty(t, network.Resources) + assert.Equal(t, 0, network.RoutingPeersCount) + }, + }, + { + name: "Create network with name only", + requestBody: &api.NetworkRequest{ + Name: "simpleNetwork", + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, network *api.Network) { + t.Helper() + assert.NotEmpty(t, network.Id) + assert.Equal(t, "simpleNetwork", network.Name) + }, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, _, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/networks.sql", nil, false) + + body, err := json.Marshal(tc.requestBody) + require.NoError(t, err) + + req := testing_tools.BuildRequest(t, body, http.MethodPost, "/api/networks", user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.verifyResponse != nil { + got := &api.Network{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + tc.verifyResponse(t, got) + } + }) + } + } +} + +func Test_Networks_Update(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + updatedDesc := "updated description" + + tt := []struct { + name string + networkId string + requestBody *api.NetworkRequest + expectedStatus int + verifyResponse func(t *testing.T, network *api.Network) + }{ + { + name: "Update network name", + networkId: "testNetworkId", + requestBody: &api.NetworkRequest{ + Name: "updatedNetwork", + Description: &updatedDesc, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, network *api.Network) { + t.Helper() + assert.Equal(t, "testNetworkId", network.Id) + assert.Equal(t, "updatedNetwork", network.Name) + assert.Equal(t, "updated description", *network.Description) + }, + }, + { + name: "Update non-existing network", + networkId: "nonExistingNetworkId", + requestBody: &api.NetworkRequest{ + Name: "whatever", + }, + expectedStatus: http.StatusNotFound, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, _, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/networks.sql", nil, false) + + body, err := json.Marshal(tc.requestBody) + require.NoError(t, err) + + req := testing_tools.BuildRequest(t, body, http.MethodPut, strings.Replace("/api/networks/{networkId}", "{networkId}", tc.networkId, 1), user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.verifyResponse != nil { + got := &api.Network{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + tc.verifyResponse(t, got) + } + }) + } + } +} + +func Test_Networks_Delete(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + tt := []struct { + name string + networkId string + expectedStatus int + }{ + { + name: "Delete existing network", + networkId: "testNetworkId", + expectedStatus: http.StatusOK, + }, + { + name: "Delete non-existing network", + networkId: "nonExistingNetworkId", + expectedStatus: http.StatusNotFound, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, _, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/networks.sql", nil, false) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodDelete, strings.Replace("/api/networks/{networkId}", "{networkId}", tc.networkId, 1), user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + }) + } + } +} + +func Test_Networks_Delete_Cascades(t *testing.T) { + apiHandler, _, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/networks.sql", nil, false) + + // Delete the network + req := testing_tools.BuildRequest(t, []byte{}, http.MethodDelete, "/api/networks/testNetworkId", testing_tools.TestAdminId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + testing_tools.ReadResponse(t, recorder, http.StatusOK, true) + + // Verify network is gone + req = testing_tools.BuildRequest(t, []byte{}, http.MethodGet, "/api/networks/testNetworkId", testing_tools.TestAdminId) + recorder = httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + testing_tools.ReadResponse(t, recorder, http.StatusNotFound, true) + + // Verify routers in that network are gone + req = testing_tools.BuildRequest(t, []byte{}, http.MethodGet, "/api/networks/testNetworkId/routers", testing_tools.TestAdminId) + recorder = httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + content, _ := testing_tools.ReadResponse(t, recorder, http.StatusOK, true) + var routers []*api.NetworkRouter + require.NoError(t, json.Unmarshal(content, &routers)) + assert.Empty(t, routers) + + // Verify resources in that network are gone + req = testing_tools.BuildRequest(t, []byte{}, http.MethodGet, "/api/networks/testNetworkId/resources", testing_tools.TestAdminId) + recorder = httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + content, _ = testing_tools.ReadResponse(t, recorder, http.StatusOK, true) + var resources []*api.NetworkResource + require.NoError(t, json.Unmarshal(content, &resources)) + assert.Empty(t, resources) +} + +func Test_NetworkResources_GetAllInNetwork(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + for _, user := range users { + t.Run(user.name+" - Get all resources in network", func(t *testing.T) { + apiHandler, _, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/networks.sql", nil, true) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, "/api/networks/testNetworkId/resources", user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, http.StatusOK, user.expectResponse) + if !expectResponse { + return + } + + got := []*api.NetworkResource{} + if err := json.Unmarshal(content, &got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + + assert.Equal(t, 1, len(got)) + assert.Equal(t, "testResourceId", got[0].Id) + assert.Equal(t, "testResource", got[0].Name) + assert.Equal(t, api.NetworkResourceType("host"), got[0].Type) + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } +} + +func Test_NetworkResources_GetAllInAccount(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + for _, user := range users { + t.Run(user.name+" - Get all resources in account", func(t *testing.T) { + apiHandler, _, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/networks.sql", nil, true) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, "/api/networks/resources", user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, http.StatusOK, user.expectResponse) + if !expectResponse { + return + } + + got := []*api.NetworkResource{} + if err := json.Unmarshal(content, &got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + + assert.GreaterOrEqual(t, len(got), 1) + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } +} + +func Test_NetworkResources_GetById(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + tt := []struct { + name string + networkId string + resourceId string + expectedStatus int + expectResource bool + }{ + { + name: "Get existing resource", + networkId: "testNetworkId", + resourceId: "testResourceId", + expectedStatus: http.StatusOK, + expectResource: true, + }, + { + name: "Get non-existing resource", + networkId: "testNetworkId", + resourceId: "nonExistingResourceId", + expectedStatus: http.StatusNotFound, + expectResource: false, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, _, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/networks.sql", nil, true) + + path := fmt.Sprintf("/api/networks/%s/resources/%s", tc.networkId, tc.resourceId) + req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, path, user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.expectResource { + got := &api.NetworkResource{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + assert.Equal(t, tc.resourceId, got.Id) + assert.Equal(t, "testResource", got.Name) + assert.Equal(t, api.NetworkResourceType("host"), got.Type) + assert.Equal(t, "3.3.3.3/32", got.Address) + } + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } + } +} + +func Test_NetworkResources_Create(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + desc := "new resource" + + tt := []struct { + name string + networkId string + requestBody *api.NetworkResourceRequest + expectedStatus int + verifyResponse func(t *testing.T, resource *api.NetworkResource) + }{ + { + name: "Create host resource with IP", + networkId: "testNetworkId", + requestBody: &api.NetworkResourceRequest{ + Name: "hostResource", + Description: &desc, + Address: "1.1.1.1", + Groups: []string{testing_tools.TestGroupId}, + Enabled: true, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, resource *api.NetworkResource) { + t.Helper() + assert.NotEmpty(t, resource.Id) + assert.Equal(t, "hostResource", resource.Name) + assert.Equal(t, api.NetworkResourceType("host"), resource.Type) + assert.Equal(t, "1.1.1.1/32", resource.Address) + assert.True(t, resource.Enabled) + }, + }, + { + name: "Create host resource with CIDR /32", + networkId: "testNetworkId", + requestBody: &api.NetworkResourceRequest{ + Name: "hostCIDR", + Address: "10.0.0.1/32", + Groups: []string{testing_tools.TestGroupId}, + Enabled: true, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, resource *api.NetworkResource) { + t.Helper() + assert.Equal(t, api.NetworkResourceType("host"), resource.Type) + assert.Equal(t, "10.0.0.1/32", resource.Address) + }, + }, + { + name: "Create subnet resource", + networkId: "testNetworkId", + requestBody: &api.NetworkResourceRequest{ + Name: "subnetResource", + Address: "192.168.0.0/24", + Groups: []string{testing_tools.TestGroupId}, + Enabled: true, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, resource *api.NetworkResource) { + t.Helper() + assert.Equal(t, api.NetworkResourceType("subnet"), resource.Type) + assert.Equal(t, "192.168.0.0/24", resource.Address) + }, + }, + { + name: "Create domain resource", + networkId: "testNetworkId", + requestBody: &api.NetworkResourceRequest{ + Name: "domainResource", + Address: "example.com", + Groups: []string{testing_tools.TestGroupId}, + Enabled: true, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, resource *api.NetworkResource) { + t.Helper() + assert.Equal(t, api.NetworkResourceType("domain"), resource.Type) + assert.Equal(t, "example.com", resource.Address) + }, + }, + { + name: "Create wildcard domain resource", + networkId: "testNetworkId", + requestBody: &api.NetworkResourceRequest{ + Name: "wildcardDomain", + Address: "*.example.com", + Groups: []string{testing_tools.TestGroupId}, + Enabled: true, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, resource *api.NetworkResource) { + t.Helper() + assert.Equal(t, api.NetworkResourceType("domain"), resource.Type) + assert.Equal(t, "*.example.com", resource.Address) + }, + }, + { + name: "Create disabled resource", + networkId: "testNetworkId", + requestBody: &api.NetworkResourceRequest{ + Name: "disabledResource", + Address: "5.5.5.5", + Groups: []string{testing_tools.TestGroupId}, + Enabled: false, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, resource *api.NetworkResource) { + t.Helper() + assert.False(t, resource.Enabled) + }, + }, + { + name: "Create resource with invalid address", + networkId: "testNetworkId", + requestBody: &api.NetworkResourceRequest{ + Name: "invalidResource", + Address: "not-a-valid-address!!!", + Groups: []string{testing_tools.TestGroupId}, + Enabled: true, + }, + expectedStatus: http.StatusInternalServerError, + }, + { + name: "Create resource with empty groups", + networkId: "testNetworkId", + requestBody: &api.NetworkResourceRequest{ + Name: "noGroupsResource", + Address: "7.7.7.7", + Groups: []string{}, + Enabled: true, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, resource *api.NetworkResource) { + t.Helper() + assert.NotEmpty(t, resource.Id) + }, + }, + { + name: "Create resource with duplicate name", + networkId: "testNetworkId", + requestBody: &api.NetworkResourceRequest{ + Name: "testResource", + Address: "8.8.8.8", + Groups: []string{testing_tools.TestGroupId}, + Enabled: true, + }, + expectedStatus: http.StatusUnprocessableEntity, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, _, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/networks.sql", nil, false) + + body, err := json.Marshal(tc.requestBody) + require.NoError(t, err) + + path := fmt.Sprintf("/api/networks/%s/resources", tc.networkId) + req := testing_tools.BuildRequest(t, body, http.MethodPost, path, user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.verifyResponse != nil { + got := &api.NetworkResource{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + tc.verifyResponse(t, got) + } + }) + } + } +} + +func Test_NetworkResources_Update(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + updatedDesc := "updated resource" + + tt := []struct { + name string + networkId string + resourceId string + requestBody *api.NetworkResourceRequest + expectedStatus int + verifyResponse func(t *testing.T, resource *api.NetworkResource) + }{ + { + name: "Update resource name and address", + networkId: "testNetworkId", + resourceId: "testResourceId", + requestBody: &api.NetworkResourceRequest{ + Name: "updatedResource", + Description: &updatedDesc, + Address: "4.4.4.4", + Groups: []string{testing_tools.TestGroupId}, + Enabled: true, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, resource *api.NetworkResource) { + t.Helper() + assert.Equal(t, "testResourceId", resource.Id) + assert.Equal(t, "updatedResource", resource.Name) + assert.Equal(t, "updated resource", *resource.Description) + assert.Equal(t, "4.4.4.4/32", resource.Address) + }, + }, + { + name: "Update resource to subnet type", + networkId: "testNetworkId", + resourceId: "testResourceId", + requestBody: &api.NetworkResourceRequest{ + Name: "testResource", + Address: "10.0.0.0/16", + Groups: []string{testing_tools.TestGroupId}, + Enabled: true, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, resource *api.NetworkResource) { + t.Helper() + assert.Equal(t, api.NetworkResourceType("subnet"), resource.Type) + assert.Equal(t, "10.0.0.0/16", resource.Address) + }, + }, + { + name: "Update resource to domain type", + networkId: "testNetworkId", + resourceId: "testResourceId", + requestBody: &api.NetworkResourceRequest{ + Name: "testResource", + Address: "myservice.example.com", + Groups: []string{testing_tools.TestGroupId}, + Enabled: true, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, resource *api.NetworkResource) { + t.Helper() + assert.Equal(t, api.NetworkResourceType("domain"), resource.Type) + assert.Equal(t, "myservice.example.com", resource.Address) + }, + }, + { + name: "Update non-existing resource", + networkId: "testNetworkId", + resourceId: "nonExistingResourceId", + requestBody: &api.NetworkResourceRequest{ + Name: "whatever", + Address: "1.2.3.4", + Groups: []string{testing_tools.TestGroupId}, + Enabled: true, + }, + expectedStatus: http.StatusNotFound, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, _, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/networks.sql", nil, false) + + body, err := json.Marshal(tc.requestBody) + require.NoError(t, err) + + path := fmt.Sprintf("/api/networks/%s/resources/%s", tc.networkId, tc.resourceId) + req := testing_tools.BuildRequest(t, body, http.MethodPut, path, user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.verifyResponse != nil { + got := &api.NetworkResource{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + tc.verifyResponse(t, got) + } + }) + } + } +} + +func Test_NetworkResources_Delete(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + tt := []struct { + name string + networkId string + resourceId string + expectedStatus int + }{ + { + name: "Delete existing resource", + networkId: "testNetworkId", + resourceId: "testResourceId", + expectedStatus: http.StatusOK, + }, + { + name: "Delete non-existing resource", + networkId: "testNetworkId", + resourceId: "nonExistingResourceId", + expectedStatus: http.StatusNotFound, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, _, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/networks.sql", nil, false) + + path := fmt.Sprintf("/api/networks/%s/resources/%s", tc.networkId, tc.resourceId) + req := testing_tools.BuildRequest(t, []byte{}, http.MethodDelete, path, user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + }) + } + } +} + +func Test_NetworkRouters_GetAllInNetwork(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + for _, user := range users { + t.Run(user.name+" - Get all routers in network", func(t *testing.T) { + apiHandler, _, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/networks.sql", nil, true) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, "/api/networks/testNetworkId/routers", user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, http.StatusOK, user.expectResponse) + if !expectResponse { + return + } + + got := []*api.NetworkRouter{} + if err := json.Unmarshal(content, &got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + + assert.Equal(t, 1, len(got)) + assert.Equal(t, "testRouterId", got[0].Id) + assert.Equal(t, "testPeerId", *got[0].Peer) + assert.True(t, got[0].Masquerade) + assert.Equal(t, 100, got[0].Metric) + assert.True(t, got[0].Enabled) + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } +} + +func Test_NetworkRouters_GetAllInAccount(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + for _, user := range users { + t.Run(user.name+" - Get all routers in account", func(t *testing.T) { + apiHandler, _, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/networks.sql", nil, true) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, "/api/networks/routers", user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, http.StatusOK, user.expectResponse) + if !expectResponse { + return + } + + got := []*api.NetworkRouter{} + if err := json.Unmarshal(content, &got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + + assert.GreaterOrEqual(t, len(got), 1) + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } +} + +func Test_NetworkRouters_GetById(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + tt := []struct { + name string + networkId string + routerId string + expectedStatus int + expectRouter bool + }{ + { + name: "Get existing router", + networkId: "testNetworkId", + routerId: "testRouterId", + expectedStatus: http.StatusOK, + expectRouter: true, + }, + { + name: "Get non-existing router", + networkId: "testNetworkId", + routerId: "nonExistingRouterId", + expectedStatus: http.StatusNotFound, + expectRouter: false, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, _, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/networks.sql", nil, true) + + path := fmt.Sprintf("/api/networks/%s/routers/%s", tc.networkId, tc.routerId) + req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, path, user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.expectRouter { + got := &api.NetworkRouter{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + assert.Equal(t, tc.routerId, got.Id) + assert.Equal(t, "testPeerId", *got.Peer) + assert.True(t, got.Masquerade) + assert.Equal(t, 100, got.Metric) + assert.True(t, got.Enabled) + } + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } + } +} + +func Test_NetworkRouters_Create(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + peerID := "testPeerId" + peerGroups := []string{testing_tools.TestGroupId} + + tt := []struct { + name string + networkId string + requestBody *api.NetworkRouterRequest + expectedStatus int + verifyResponse func(t *testing.T, router *api.NetworkRouter) + }{ + { + name: "Create router with peer", + networkId: "testNetworkId", + requestBody: &api.NetworkRouterRequest{ + Peer: &peerID, + Masquerade: true, + Metric: 200, + Enabled: true, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, router *api.NetworkRouter) { + t.Helper() + assert.NotEmpty(t, router.Id) + assert.Equal(t, peerID, *router.Peer) + assert.True(t, router.Masquerade) + assert.Equal(t, 200, router.Metric) + assert.True(t, router.Enabled) + }, + }, + { + name: "Create router with peer groups", + networkId: "testNetworkId", + requestBody: &api.NetworkRouterRequest{ + PeerGroups: &peerGroups, + Masquerade: false, + Metric: 300, + Enabled: true, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, router *api.NetworkRouter) { + t.Helper() + assert.NotEmpty(t, router.Id) + assert.NotNil(t, router.PeerGroups) + assert.Equal(t, 1, len(*router.PeerGroups)) + assert.False(t, router.Masquerade) + assert.Equal(t, 300, router.Metric) + assert.True(t, router.Enabled) // always true on creation + }, + }, + { + name: "Create router with both peer and peer_groups", + networkId: "testNetworkId", + requestBody: &api.NetworkRouterRequest{ + Peer: &peerID, + PeerGroups: &peerGroups, + Masquerade: true, + Metric: 100, + Enabled: true, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, router *api.NetworkRouter) { + t.Helper() + assert.NotEmpty(t, router.Id) + assert.Equal(t, peerID, *router.Peer) + assert.Equal(t, 1, len(*router.PeerGroups)) + }, + }, + { + name: "Create router in non-existing network", + networkId: "nonExistingNetworkId", + requestBody: &api.NetworkRouterRequest{ + Peer: &peerID, + Masquerade: true, + Metric: 100, + Enabled: true, + }, + expectedStatus: http.StatusNotFound, + }, + { + name: "Create router enabled is always true", + networkId: "testNetworkId", + requestBody: &api.NetworkRouterRequest{ + Peer: &peerID, + Masquerade: false, + Metric: 50, + Enabled: false, // handler sets to true + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, router *api.NetworkRouter) { + t.Helper() + assert.True(t, router.Enabled) // always true on creation + }, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, _, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/networks.sql", nil, false) + + body, err := json.Marshal(tc.requestBody) + require.NoError(t, err) + + path := fmt.Sprintf("/api/networks/%s/routers", tc.networkId) + req := testing_tools.BuildRequest(t, body, http.MethodPost, path, user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.verifyResponse != nil { + got := &api.NetworkRouter{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + tc.verifyResponse(t, got) + } + }) + } + } +} + +func Test_NetworkRouters_Update(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + peerID := "testPeerId" + peerGroups := []string{testing_tools.TestGroupId} + + tt := []struct { + name string + networkId string + routerId string + requestBody *api.NetworkRouterRequest + expectedStatus int + verifyResponse func(t *testing.T, router *api.NetworkRouter) + }{ + { + name: "Update router metric and masquerade", + networkId: "testNetworkId", + routerId: "testRouterId", + requestBody: &api.NetworkRouterRequest{ + Peer: &peerID, + Masquerade: false, + Metric: 500, + Enabled: true, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, router *api.NetworkRouter) { + t.Helper() + assert.Equal(t, "testRouterId", router.Id) + assert.False(t, router.Masquerade) + assert.Equal(t, 500, router.Metric) + }, + }, + { + name: "Update router to use peer groups", + networkId: "testNetworkId", + routerId: "testRouterId", + requestBody: &api.NetworkRouterRequest{ + PeerGroups: &peerGroups, + Masquerade: true, + Metric: 100, + Enabled: true, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, router *api.NetworkRouter) { + t.Helper() + assert.NotNil(t, router.PeerGroups) + assert.Equal(t, 1, len(*router.PeerGroups)) + }, + }, + { + name: "Update router disabled", + networkId: "testNetworkId", + routerId: "testRouterId", + requestBody: &api.NetworkRouterRequest{ + Peer: &peerID, + Masquerade: true, + Metric: 100, + Enabled: false, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, router *api.NetworkRouter) { + t.Helper() + assert.False(t, router.Enabled) + }, + }, + { + name: "Update non-existing router creates it", + networkId: "testNetworkId", + routerId: "nonExistingRouterId", + requestBody: &api.NetworkRouterRequest{ + Peer: &peerID, + Masquerade: true, + Metric: 100, + Enabled: true, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, router *api.NetworkRouter) { + t.Helper() + assert.Equal(t, "nonExistingRouterId", router.Id) + }, + }, + { + name: "Update router with both peer and peer_groups", + networkId: "testNetworkId", + routerId: "testRouterId", + requestBody: &api.NetworkRouterRequest{ + Peer: &peerID, + PeerGroups: &peerGroups, + Masquerade: true, + Metric: 100, + Enabled: true, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, router *api.NetworkRouter) { + t.Helper() + assert.Equal(t, "testRouterId", router.Id) + assert.Equal(t, peerID, *router.Peer) + assert.Equal(t, 1, len(*router.PeerGroups)) + }, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, _, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/networks.sql", nil, false) + + body, err := json.Marshal(tc.requestBody) + require.NoError(t, err) + + path := fmt.Sprintf("/api/networks/%s/routers/%s", tc.networkId, tc.routerId) + req := testing_tools.BuildRequest(t, body, http.MethodPut, path, user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.verifyResponse != nil { + got := &api.NetworkRouter{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + tc.verifyResponse(t, got) + } + }) + } + } +} + +func Test_NetworkRouters_Delete(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + tt := []struct { + name string + networkId string + routerId string + expectedStatus int + }{ + { + name: "Delete existing router", + networkId: "testNetworkId", + routerId: "testRouterId", + expectedStatus: http.StatusOK, + }, + { + name: "Delete non-existing router", + networkId: "testNetworkId", + routerId: "nonExistingRouterId", + expectedStatus: http.StatusNotFound, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, _, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/networks.sql", nil, false) + + path := fmt.Sprintf("/api/networks/%s/routers/%s", tc.networkId, tc.routerId) + req := testing_tools.BuildRequest(t, []byte{}, http.MethodDelete, path, user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + }) + } + } +} diff --git a/management/server/http/testing/integration/peers_handler_integration_test.go b/management/server/http/testing/integration/peers_handler_integration_test.go new file mode 100644 index 000000000..17a9e94a6 --- /dev/null +++ b/management/server/http/testing/integration/peers_handler_integration_test.go @@ -0,0 +1,605 @@ +//go:build integration + +package integration + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/netbirdio/netbird/management/server/http/testing/testing_tools" + "github.com/netbirdio/netbird/management/server/http/testing/testing_tools/channel" + "github.com/netbirdio/netbird/shared/management/http/api" +) + +const ( + testPeerId2 = "testPeerId2" +) + +func Test_Peers_GetAll(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + { + name: "Regular user", + userId: testing_tools.TestUserId, + expectResponse: false, + }, + { + name: "Admin user", + userId: testing_tools.TestAdminId, + expectResponse: true, + }, + { + name: "Owner user", + userId: testing_tools.TestOwnerId, + expectResponse: true, + }, + { + name: "Regular service user", + userId: testing_tools.TestServiceUserId, + expectResponse: true, + }, + { + name: "Admin service user", + userId: testing_tools.TestServiceAdminId, + expectResponse: true, + }, + { + name: "Blocked user", + userId: testing_tools.BlockedUserId, + expectResponse: false, + }, + { + name: "Other user", + userId: testing_tools.OtherUserId, + expectResponse: false, + }, + { + name: "Invalid token", + userId: testing_tools.InvalidToken, + expectResponse: false, + }, + } + + for _, user := range users { + t.Run(user.name+" - Get all peers", func(t *testing.T) { + apiHandler, _, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/peers_integration.sql", nil, true) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, "/api/peers", user.userId) + recorder := httptest.NewRecorder() + + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, http.StatusOK, user.expectResponse) + if !expectResponse { + return + } + + var got []api.PeerBatch + if err := json.Unmarshal(content, &got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + + assert.GreaterOrEqual(t, len(got), 2, "Expected at least 2 peers") + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } +} + +func Test_Peers_GetById(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + { + name: "Regular user", + userId: testing_tools.TestUserId, + expectResponse: false, + }, + { + name: "Admin user", + userId: testing_tools.TestAdminId, + expectResponse: true, + }, + { + name: "Owner user", + userId: testing_tools.TestOwnerId, + expectResponse: true, + }, + { + name: "Regular service user", + userId: testing_tools.TestServiceUserId, + expectResponse: true, + }, + { + name: "Admin service user", + userId: testing_tools.TestServiceAdminId, + expectResponse: true, + }, + { + name: "Blocked user", + userId: testing_tools.BlockedUserId, + expectResponse: false, + }, + { + name: "Other user", + userId: testing_tools.OtherUserId, + expectResponse: false, + }, + { + name: "Invalid token", + userId: testing_tools.InvalidToken, + expectResponse: false, + }, + } + + tt := []struct { + name string + expectedStatus int + requestType string + requestPath string + requestId string + verifyResponse func(t *testing.T, peer *api.Peer) + }{ + { + name: "Get existing peer", + requestType: http.MethodGet, + requestPath: "/api/peers/{peerId}", + requestId: testing_tools.TestPeerId, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, peer *api.Peer) { + t.Helper() + assert.Equal(t, testing_tools.TestPeerId, peer.Id) + assert.Equal(t, "test-peer-1", peer.Name) + assert.Equal(t, "test-host-1", peer.Hostname) + assert.Equal(t, "Debian GNU/Linux ", peer.Os) + assert.Equal(t, "0.12.0", peer.Version) + assert.Equal(t, false, peer.SshEnabled) + assert.Equal(t, true, peer.LoginExpirationEnabled) + }, + }, + { + name: "Get second existing peer", + requestType: http.MethodGet, + requestPath: "/api/peers/{peerId}", + requestId: testPeerId2, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, peer *api.Peer) { + t.Helper() + assert.Equal(t, testPeerId2, peer.Id) + assert.Equal(t, "test-peer-2", peer.Name) + assert.Equal(t, "test-host-2", peer.Hostname) + assert.Equal(t, "Ubuntu ", peer.Os) + assert.Equal(t, true, peer.SshEnabled) + assert.Equal(t, false, peer.LoginExpirationEnabled) + assert.Equal(t, true, peer.Connected) + }, + }, + { + name: "Get non-existing peer", + requestType: http.MethodGet, + requestPath: "/api/peers/{peerId}", + requestId: "nonExistingPeerId", + expectedStatus: http.StatusNotFound, + verifyResponse: nil, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, _, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/peers_integration.sql", nil, true) + + req := testing_tools.BuildRequest(t, []byte{}, tc.requestType, strings.Replace(tc.requestPath, "{peerId}", tc.requestId, 1), user.userId) + recorder := httptest.NewRecorder() + + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.verifyResponse != nil { + got := &api.Peer{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + tc.verifyResponse(t, got) + } + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } + } +} + +func Test_Peers_Update(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + { + name: "Regular user", + userId: testing_tools.TestUserId, + expectResponse: false, + }, + { + name: "Admin user", + userId: testing_tools.TestAdminId, + expectResponse: true, + }, + { + name: "Owner user", + userId: testing_tools.TestOwnerId, + expectResponse: true, + }, + { + name: "Regular service user", + userId: testing_tools.TestServiceUserId, + expectResponse: false, + }, + { + name: "Admin service user", + userId: testing_tools.TestServiceAdminId, + expectResponse: true, + }, + { + name: "Blocked user", + userId: testing_tools.BlockedUserId, + expectResponse: false, + }, + { + name: "Other user", + userId: testing_tools.OtherUserId, + expectResponse: false, + }, + { + name: "Invalid token", + userId: testing_tools.InvalidToken, + expectResponse: false, + }, + } + + tt := []struct { + name string + expectedStatus int + requestBody *api.PeerRequest + requestType string + requestPath string + requestId string + verifyResponse func(t *testing.T, peer *api.Peer) + }{ + { + name: "Update peer name", + requestType: http.MethodPut, + requestPath: "/api/peers/{peerId}", + requestId: testing_tools.TestPeerId, + requestBody: &api.PeerRequest{ + Name: "updated-peer-name", + SshEnabled: false, + LoginExpirationEnabled: true, + InactivityExpirationEnabled: false, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, peer *api.Peer) { + t.Helper() + assert.Equal(t, testing_tools.TestPeerId, peer.Id) + assert.Equal(t, "updated-peer-name", peer.Name) + assert.Equal(t, false, peer.SshEnabled) + assert.Equal(t, true, peer.LoginExpirationEnabled) + }, + }, + { + name: "Enable SSH on peer", + requestType: http.MethodPut, + requestPath: "/api/peers/{peerId}", + requestId: testing_tools.TestPeerId, + requestBody: &api.PeerRequest{ + Name: "test-peer-1", + SshEnabled: true, + LoginExpirationEnabled: true, + InactivityExpirationEnabled: false, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, peer *api.Peer) { + t.Helper() + assert.Equal(t, testing_tools.TestPeerId, peer.Id) + assert.Equal(t, "test-peer-1", peer.Name) + assert.Equal(t, true, peer.SshEnabled) + assert.Equal(t, true, peer.LoginExpirationEnabled) + }, + }, + { + name: "Disable login expiration on peer", + requestType: http.MethodPut, + requestPath: "/api/peers/{peerId}", + requestId: testing_tools.TestPeerId, + requestBody: &api.PeerRequest{ + Name: "test-peer-1", + SshEnabled: false, + LoginExpirationEnabled: false, + InactivityExpirationEnabled: false, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, peer *api.Peer) { + t.Helper() + assert.Equal(t, testing_tools.TestPeerId, peer.Id) + assert.Equal(t, false, peer.LoginExpirationEnabled) + }, + }, + { + name: "Update non-existing peer", + requestType: http.MethodPut, + requestPath: "/api/peers/{peerId}", + requestId: "nonExistingPeerId", + requestBody: &api.PeerRequest{ + Name: "updated-name", + SshEnabled: false, + LoginExpirationEnabled: false, + InactivityExpirationEnabled: false, + }, + expectedStatus: http.StatusNotFound, + verifyResponse: nil, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, am, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/peers_integration.sql", nil, false) + + body, err := json.Marshal(tc.requestBody) + if err != nil { + t.Fatalf("Failed to marshal request body: %v", err) + } + + req := testing_tools.BuildRequest(t, body, tc.requestType, strings.Replace(tc.requestPath, "{peerId}", tc.requestId, 1), user.userId) + recorder := httptest.NewRecorder() + + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.verifyResponse != nil { + got := &api.Peer{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + tc.verifyResponse(t, got) + + // Verify updated peer in DB + db := testing_tools.GetDB(t, am.GetStore()) + dbPeer := testing_tools.VerifyPeerInDB(t, db, tc.requestId) + assert.Equal(t, tc.requestBody.Name, dbPeer.Name) + assert.Equal(t, tc.requestBody.SshEnabled, dbPeer.SSHEnabled) + assert.Equal(t, tc.requestBody.LoginExpirationEnabled, dbPeer.LoginExpirationEnabled) + assert.Equal(t, tc.requestBody.InactivityExpirationEnabled, dbPeer.InactivityExpirationEnabled) + } + }) + } + } +} + +func Test_Peers_Delete(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + { + name: "Regular user", + userId: testing_tools.TestUserId, + expectResponse: false, + }, + { + name: "Admin user", + userId: testing_tools.TestAdminId, + expectResponse: true, + }, + { + name: "Owner user", + userId: testing_tools.TestOwnerId, + expectResponse: true, + }, + { + name: "Regular service user", + userId: testing_tools.TestServiceUserId, + expectResponse: false, + }, + { + name: "Admin service user", + userId: testing_tools.TestServiceAdminId, + expectResponse: true, + }, + { + name: "Blocked user", + userId: testing_tools.BlockedUserId, + expectResponse: false, + }, + { + name: "Other user", + userId: testing_tools.OtherUserId, + expectResponse: false, + }, + { + name: "Invalid token", + userId: testing_tools.InvalidToken, + expectResponse: false, + }, + } + + tt := []struct { + name string + expectedStatus int + requestType string + requestPath string + requestId string + }{ + { + name: "Delete existing peer", + requestType: http.MethodDelete, + requestPath: "/api/peers/{peerId}", + requestId: testPeerId2, + expectedStatus: http.StatusOK, + }, + { + name: "Delete non-existing peer", + requestType: http.MethodDelete, + requestPath: "/api/peers/{peerId}", + requestId: "nonExistingPeerId", + expectedStatus: http.StatusNotFound, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, am, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/peers_integration.sql", nil, false) + + req := testing_tools.BuildRequest(t, []byte{}, tc.requestType, strings.Replace(tc.requestPath, "{peerId}", tc.requestId, 1), user.userId) + recorder := httptest.NewRecorder() + + apiHandler.ServeHTTP(recorder, req) + + _, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + // Verify peer is actually deleted in DB + if tc.expectedStatus == http.StatusOK { + db := testing_tools.GetDB(t, am.GetStore()) + testing_tools.VerifyPeerNotInDB(t, db, tc.requestId) + } + }) + } + } +} + +func Test_Peers_GetAccessiblePeers(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + { + name: "Regular user", + userId: testing_tools.TestUserId, + expectResponse: false, + }, + { + name: "Admin user", + userId: testing_tools.TestAdminId, + expectResponse: true, + }, + { + name: "Owner user", + userId: testing_tools.TestOwnerId, + expectResponse: true, + }, + { + name: "Regular service user", + userId: testing_tools.TestServiceUserId, + expectResponse: false, + }, + { + name: "Admin service user", + userId: testing_tools.TestServiceAdminId, + expectResponse: true, + }, + { + name: "Blocked user", + userId: testing_tools.BlockedUserId, + expectResponse: false, + }, + { + name: "Other user", + userId: testing_tools.OtherUserId, + expectResponse: false, + }, + { + name: "Invalid token", + userId: testing_tools.InvalidToken, + expectResponse: false, + }, + } + + tt := []struct { + name string + expectedStatus int + requestType string + requestPath string + requestId string + }{ + { + name: "Get accessible peers for existing peer", + requestType: http.MethodGet, + requestPath: "/api/peers/{peerId}/accessible-peers", + requestId: testing_tools.TestPeerId, + expectedStatus: http.StatusOK, + }, + { + name: "Get accessible peers for non-existing peer", + requestType: http.MethodGet, + requestPath: "/api/peers/{peerId}/accessible-peers", + requestId: "nonExistingPeerId", + expectedStatus: http.StatusOK, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, _, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/peers_integration.sql", nil, true) + + req := testing_tools.BuildRequest(t, []byte{}, tc.requestType, strings.Replace(tc.requestPath, "{peerId}", tc.requestId, 1), user.userId) + recorder := httptest.NewRecorder() + + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.expectedStatus == http.StatusOK { + var got []api.AccessiblePeer + if err := json.Unmarshal(content, &got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + // The accessible peers list should be a valid array (may be empty if no policies connect peers) + assert.NotNil(t, got, "Expected accessible peers to be a valid array") + } + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } + } +} diff --git a/management/server/http/testing/integration/policies_handler_integration_test.go b/management/server/http/testing/integration/policies_handler_integration_test.go new file mode 100644 index 000000000..6f3624fb5 --- /dev/null +++ b/management/server/http/testing/integration/policies_handler_integration_test.go @@ -0,0 +1,488 @@ +//go:build integration + +package integration + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/netbirdio/netbird/management/server/http/testing/testing_tools" + "github.com/netbirdio/netbird/management/server/http/testing/testing_tools/channel" + "github.com/netbirdio/netbird/shared/management/http/api" +) + +func Test_Policies_GetAll(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + for _, user := range users { + t.Run(user.name+" - Get all policies", func(t *testing.T) { + apiHandler, _, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/policies.sql", nil, true) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, "/api/policies", user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, http.StatusOK, user.expectResponse) + if !expectResponse { + return + } + + got := []api.Policy{} + if err := json.Unmarshal(content, &got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + + assert.Equal(t, 1, len(got)) + assert.Equal(t, "testPolicy", got[0].Name) + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } +} + +func Test_Policies_GetById(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + tt := []struct { + name string + policyId string + expectedStatus int + expectPolicy bool + }{ + { + name: "Get existing policy", + policyId: "testPolicyId", + expectedStatus: http.StatusOK, + expectPolicy: true, + }, + { + name: "Get non-existing policy", + policyId: "nonExistingPolicyId", + expectedStatus: http.StatusNotFound, + expectPolicy: false, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, _, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/policies.sql", nil, true) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, strings.Replace("/api/policies/{policyId}", "{policyId}", tc.policyId, 1), user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.expectPolicy { + got := &api.Policy{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + assert.NotNil(t, got.Id) + assert.Equal(t, tc.policyId, *got.Id) + assert.Equal(t, "testPolicy", got.Name) + assert.Equal(t, true, got.Enabled) + assert.GreaterOrEqual(t, len(got.Rules), 1) + } + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } + } +} + +func Test_Policies_Create(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + srcGroups := []string{testing_tools.TestGroupId} + dstGroups := []string{testing_tools.TestGroupId} + + tt := []struct { + name string + requestBody *api.PolicyCreate + expectedStatus int + verifyResponse func(t *testing.T, policy *api.Policy) + }{ + { + name: "Create policy with accept rule", + requestBody: &api.PolicyCreate{ + Name: "newPolicy", + Enabled: true, + Rules: []api.PolicyRuleUpdate{ + { + Name: "allowAll", + Enabled: true, + Action: "accept", + Protocol: "all", + Bidirectional: true, + Sources: &srcGroups, + Destinations: &dstGroups, + }, + }, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, policy *api.Policy) { + t.Helper() + assert.NotNil(t, policy.Id) + assert.Equal(t, "newPolicy", policy.Name) + assert.Equal(t, true, policy.Enabled) + assert.Equal(t, 1, len(policy.Rules)) + assert.Equal(t, "allowAll", policy.Rules[0].Name) + }, + }, + { + name: "Create policy with drop rule", + requestBody: &api.PolicyCreate{ + Name: "dropPolicy", + Enabled: true, + Rules: []api.PolicyRuleUpdate{ + { + Name: "dropAll", + Enabled: true, + Action: "drop", + Protocol: "all", + Bidirectional: true, + Sources: &srcGroups, + Destinations: &dstGroups, + }, + }, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, policy *api.Policy) { + t.Helper() + assert.Equal(t, "dropPolicy", policy.Name) + }, + }, + { + name: "Create policy with TCP rule and ports", + requestBody: &api.PolicyCreate{ + Name: "tcpPolicy", + Enabled: true, + Rules: []api.PolicyRuleUpdate{ + { + Name: "tcpRule", + Enabled: true, + Action: "accept", + Protocol: "tcp", + Bidirectional: true, + Sources: &srcGroups, + Destinations: &dstGroups, + Ports: &[]string{"80", "443"}, + }, + }, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, policy *api.Policy) { + t.Helper() + assert.Equal(t, "tcpPolicy", policy.Name) + assert.NotNil(t, policy.Rules[0].Ports) + assert.Equal(t, 2, len(*policy.Rules[0].Ports)) + }, + }, + { + name: "Create policy with empty name", + requestBody: &api.PolicyCreate{ + Name: "", + Enabled: true, + Rules: []api.PolicyRuleUpdate{ + { + Name: "rule", + Enabled: true, + Action: "accept", + Protocol: "all", + Sources: &srcGroups, + Destinations: &dstGroups, + }, + }, + }, + expectedStatus: http.StatusUnprocessableEntity, + }, + { + name: "Create policy with no rules", + requestBody: &api.PolicyCreate{ + Name: "noRulesPolicy", + Enabled: true, + Rules: []api.PolicyRuleUpdate{}, + }, + expectedStatus: http.StatusUnprocessableEntity, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, am, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/policies.sql", nil, false) + + body, err := json.Marshal(tc.requestBody) + if err != nil { + t.Fatalf("Failed to marshal request body: %v", err) + } + + req := testing_tools.BuildRequest(t, body, http.MethodPost, "/api/policies", user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.verifyResponse != nil { + got := &api.Policy{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + tc.verifyResponse(t, got) + + // Verify policy exists in DB with correct fields + db := testing_tools.GetDB(t, am.GetStore()) + dbPolicy := testing_tools.VerifyPolicyInDB(t, db, *got.Id) + assert.Equal(t, tc.requestBody.Name, dbPolicy.Name) + assert.Equal(t, tc.requestBody.Enabled, dbPolicy.Enabled) + assert.Equal(t, len(tc.requestBody.Rules), len(dbPolicy.Rules)) + } + }) + } + } +} + +func Test_Policies_Update(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + srcGroups := []string{testing_tools.TestGroupId} + dstGroups := []string{testing_tools.TestGroupId} + + tt := []struct { + name string + policyId string + requestBody *api.PolicyCreate + expectedStatus int + verifyResponse func(t *testing.T, policy *api.Policy) + }{ + { + name: "Update policy name", + policyId: "testPolicyId", + requestBody: &api.PolicyCreate{ + Name: "updatedPolicy", + Enabled: true, + Rules: []api.PolicyRuleUpdate{ + { + Name: "testRule", + Enabled: true, + Action: "accept", + Protocol: "all", + Bidirectional: true, + Sources: &srcGroups, + Destinations: &dstGroups, + }, + }, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, policy *api.Policy) { + t.Helper() + assert.Equal(t, "updatedPolicy", policy.Name) + }, + }, + { + name: "Update policy enabled state", + policyId: "testPolicyId", + requestBody: &api.PolicyCreate{ + Name: "testPolicy", + Enabled: false, + Rules: []api.PolicyRuleUpdate{ + { + Name: "testRule", + Enabled: true, + Action: "accept", + Protocol: "all", + Bidirectional: true, + Sources: &srcGroups, + Destinations: &dstGroups, + }, + }, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, policy *api.Policy) { + t.Helper() + assert.Equal(t, false, policy.Enabled) + }, + }, + { + name: "Update non-existing policy", + policyId: "nonExistingPolicyId", + requestBody: &api.PolicyCreate{ + Name: "whatever", + Enabled: true, + Rules: []api.PolicyRuleUpdate{ + { + Name: "rule", + Enabled: true, + Action: "accept", + Protocol: "all", + Sources: &srcGroups, + Destinations: &dstGroups, + }, + }, + }, + expectedStatus: http.StatusNotFound, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, am, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/policies.sql", nil, false) + + body, err := json.Marshal(tc.requestBody) + if err != nil { + t.Fatalf("Failed to marshal request body: %v", err) + } + + req := testing_tools.BuildRequest(t, body, http.MethodPut, strings.Replace("/api/policies/{policyId}", "{policyId}", tc.policyId, 1), user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.verifyResponse != nil { + got := &api.Policy{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + tc.verifyResponse(t, got) + + // Verify updated policy in DB + db := testing_tools.GetDB(t, am.GetStore()) + dbPolicy := testing_tools.VerifyPolicyInDB(t, db, tc.policyId) + assert.Equal(t, tc.requestBody.Name, dbPolicy.Name) + assert.Equal(t, tc.requestBody.Enabled, dbPolicy.Enabled) + } + }) + } + } +} + +func Test_Policies_Delete(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + tt := []struct { + name string + policyId string + expectedStatus int + }{ + { + name: "Delete existing policy", + policyId: "testPolicyId", + expectedStatus: http.StatusOK, + }, + { + name: "Delete non-existing policy", + policyId: "nonExistingPolicyId", + expectedStatus: http.StatusNotFound, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, am, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/policies.sql", nil, false) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodDelete, strings.Replace("/api/policies/{policyId}", "{policyId}", tc.policyId, 1), user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + _, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if expectResponse && tc.expectedStatus == http.StatusOK { + db := testing_tools.GetDB(t, am.GetStore()) + testing_tools.VerifyPolicyNotInDB(t, db, tc.policyId) + } + }) + } + } +} diff --git a/management/server/http/testing/integration/routes_handler_integration_test.go b/management/server/http/testing/integration/routes_handler_integration_test.go new file mode 100644 index 000000000..eeb0c3025 --- /dev/null +++ b/management/server/http/testing/integration/routes_handler_integration_test.go @@ -0,0 +1,455 @@ +//go:build integration + +package integration + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/netbirdio/netbird/management/server/http/testing/testing_tools" + "github.com/netbirdio/netbird/management/server/http/testing/testing_tools/channel" + "github.com/netbirdio/netbird/route" + "github.com/netbirdio/netbird/shared/management/http/api" +) + +func Test_Routes_GetAll(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + for _, user := range users { + t.Run(user.name+" - Get all routes", func(t *testing.T) { + apiHandler, _, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/routes.sql", nil, true) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, "/api/routes", user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, http.StatusOK, user.expectResponse) + if !expectResponse { + return + } + + got := []api.Route{} + if err := json.Unmarshal(content, &got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + + assert.Equal(t, 2, len(got)) + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } +} + +func Test_Routes_GetById(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + tt := []struct { + name string + routeId string + expectedStatus int + expectRoute bool + }{ + { + name: "Get existing route", + routeId: "testRouteId", + expectedStatus: http.StatusOK, + expectRoute: true, + }, + { + name: "Get non-existing route", + routeId: "nonExistingRouteId", + expectedStatus: http.StatusNotFound, + expectRoute: false, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, _, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/routes.sql", nil, true) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, strings.Replace("/api/routes/{routeId}", "{routeId}", tc.routeId, 1), user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.expectRoute { + got := &api.Route{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + assert.Equal(t, tc.routeId, got.Id) + assert.Equal(t, "Test Network Route", got.Description) + } + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } + } +} + +func Test_Routes_Create(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + networkCIDR := "10.10.0.0/24" + peerID := testing_tools.TestPeerId + peerGroups := []string{"peerGroupId"} + + tt := []struct { + name string + requestBody *api.RouteRequest + expectedStatus int + verifyResponse func(t *testing.T, route *api.Route) + }{ + { + name: "Create network route with peer", + requestBody: &api.RouteRequest{ + Description: "New network route", + Network: &networkCIDR, + Peer: &peerID, + NetworkId: "newNet", + Metric: 100, + Masquerade: true, + Enabled: true, + Groups: []string{testing_tools.TestGroupId}, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, route *api.Route) { + t.Helper() + assert.NotEmpty(t, route.Id) + assert.Equal(t, "New network route", route.Description) + assert.Equal(t, 100, route.Metric) + assert.Equal(t, true, route.Masquerade) + assert.Equal(t, true, route.Enabled) + }, + }, + { + name: "Create network route with peer groups", + requestBody: &api.RouteRequest{ + Description: "Route with peer groups", + Network: &networkCIDR, + PeerGroups: &peerGroups, + NetworkId: "peerGroupNet", + Metric: 150, + Masquerade: false, + Enabled: true, + Groups: []string{testing_tools.TestGroupId}, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, route *api.Route) { + t.Helper() + assert.NotEmpty(t, route.Id) + assert.Equal(t, "Route with peer groups", route.Description) + }, + }, + { + name: "Create route with empty network_id", + requestBody: &api.RouteRequest{ + Description: "Empty net id", + Network: &networkCIDR, + Peer: &peerID, + NetworkId: "", + Metric: 100, + Enabled: true, + Groups: []string{testing_tools.TestGroupId}, + }, + expectedStatus: http.StatusUnprocessableEntity, + }, + { + name: "Create route with metric 0", + requestBody: &api.RouteRequest{ + Description: "Zero metric", + Network: &networkCIDR, + Peer: &peerID, + NetworkId: "zeroMetric", + Metric: 0, + Enabled: true, + Groups: []string{testing_tools.TestGroupId}, + }, + expectedStatus: http.StatusUnprocessableEntity, + }, + { + name: "Create route with metric 10000", + requestBody: &api.RouteRequest{ + Description: "High metric", + Network: &networkCIDR, + Peer: &peerID, + NetworkId: "highMetric", + Metric: 10000, + Enabled: true, + Groups: []string{testing_tools.TestGroupId}, + }, + expectedStatus: http.StatusUnprocessableEntity, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, am, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/routes.sql", nil, false) + + body, err := json.Marshal(tc.requestBody) + if err != nil { + t.Fatalf("Failed to marshal request body: %v", err) + } + + req := testing_tools.BuildRequest(t, body, http.MethodPost, "/api/routes", user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.verifyResponse != nil { + got := &api.Route{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + tc.verifyResponse(t, got) + + // Verify route exists in DB with correct fields + db := testing_tools.GetDB(t, am.GetStore()) + dbRoute := testing_tools.VerifyRouteInDB(t, db, route.ID(got.Id)) + assert.Equal(t, tc.requestBody.Description, dbRoute.Description) + assert.Equal(t, tc.requestBody.Metric, dbRoute.Metric) + assert.Equal(t, tc.requestBody.Masquerade, dbRoute.Masquerade) + assert.Equal(t, tc.requestBody.Enabled, dbRoute.Enabled) + assert.Equal(t, route.NetID(tc.requestBody.NetworkId), dbRoute.NetID) + } + }) + } + } +} + +func Test_Routes_Update(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + networkCIDR := "10.0.0.0/24" + peerID := testing_tools.TestPeerId + + tt := []struct { + name string + routeId string + requestBody *api.RouteRequest + expectedStatus int + verifyResponse func(t *testing.T, route *api.Route) + }{ + { + name: "Update route description", + routeId: "testRouteId", + requestBody: &api.RouteRequest{ + Description: "Updated description", + Network: &networkCIDR, + Peer: &peerID, + NetworkId: "testNet", + Metric: 100, + Masquerade: true, + Enabled: true, + Groups: []string{testing_tools.TestGroupId}, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, route *api.Route) { + t.Helper() + assert.Equal(t, "testRouteId", route.Id) + assert.Equal(t, "Updated description", route.Description) + }, + }, + { + name: "Update route metric", + routeId: "testRouteId", + requestBody: &api.RouteRequest{ + Description: "Test Network Route", + Network: &networkCIDR, + Peer: &peerID, + NetworkId: "testNet", + Metric: 500, + Masquerade: true, + Enabled: true, + Groups: []string{testing_tools.TestGroupId}, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, route *api.Route) { + t.Helper() + assert.Equal(t, 500, route.Metric) + }, + }, + { + name: "Update non-existing route", + routeId: "nonExistingRouteId", + requestBody: &api.RouteRequest{ + Description: "whatever", + Network: &networkCIDR, + Peer: &peerID, + NetworkId: "testNet", + Metric: 100, + Enabled: true, + Groups: []string{testing_tools.TestGroupId}, + }, + expectedStatus: http.StatusNotFound, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, am, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/routes.sql", nil, false) + + body, err := json.Marshal(tc.requestBody) + if err != nil { + t.Fatalf("Failed to marshal request body: %v", err) + } + + req := testing_tools.BuildRequest(t, body, http.MethodPut, strings.Replace("/api/routes/{routeId}", "{routeId}", tc.routeId, 1), user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.verifyResponse != nil { + got := &api.Route{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + tc.verifyResponse(t, got) + + // Verify updated route in DB + db := testing_tools.GetDB(t, am.GetStore()) + dbRoute := testing_tools.VerifyRouteInDB(t, db, route.ID(got.Id)) + assert.Equal(t, tc.requestBody.Description, dbRoute.Description) + assert.Equal(t, tc.requestBody.Metric, dbRoute.Metric) + assert.Equal(t, tc.requestBody.Masquerade, dbRoute.Masquerade) + assert.Equal(t, tc.requestBody.Enabled, dbRoute.Enabled) + } + }) + } + } +} + +func Test_Routes_Delete(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + tt := []struct { + name string + routeId string + expectedStatus int + }{ + { + name: "Delete existing route", + routeId: "testRouteId", + expectedStatus: http.StatusOK, + }, + { + name: "Delete non-existing route", + routeId: "nonExistingRouteId", + expectedStatus: http.StatusNotFound, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, am, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/routes.sql", nil, false) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodDelete, strings.Replace("/api/routes/{routeId}", "{routeId}", tc.routeId, 1), user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + + // Verify route was deleted from DB for successful deletes + if tc.expectedStatus == http.StatusOK && user.expectResponse { + db := testing_tools.GetDB(t, am.GetStore()) + testing_tools.VerifyRouteNotInDB(t, db, route.ID(tc.routeId)) + } + }) + } + } +} diff --git a/management/server/http/testing/integration/setupkeys_handler_integration_test.go b/management/server/http/testing/integration/setupkeys_handler_integration_test.go index c1a9829da..0d3aaac82 100644 --- a/management/server/http/testing/integration/setupkeys_handler_integration_test.go +++ b/management/server/http/testing/integration/setupkeys_handler_integration_test.go @@ -3,7 +3,6 @@ package integration import ( - "context" "encoding/json" "net/http" "net/http/httptest" @@ -14,7 +13,6 @@ import ( "github.com/stretchr/testify/assert" - "github.com/netbirdio/netbird/management/server/http/handlers/setup_keys" "github.com/netbirdio/netbird/management/server/http/testing/testing_tools" "github.com/netbirdio/netbird/management/server/http/testing/testing_tools/channel" "github.com/netbirdio/netbird/shared/management/http/api" @@ -254,7 +252,7 @@ func Test_SetupKeys_Create(t *testing.T) { expectedResponse: nil, }, { - name: "Create Setup Key", + name: "Create Setup Key with nil AutoGroups", requestType: http.MethodPost, requestPath: "/api/setup-keys", requestBody: &api.CreateSetupKeyRequest{ @@ -308,14 +306,15 @@ func Test_SetupKeys_Create(t *testing.T) { t.Fatalf("Sent content is not in correct json format; %v", err) } + gotID := got.Id validateCreatedKey(t, tc.expectedResponse, got) - key, err := am.GetSetupKey(context.Background(), testing_tools.TestAccountId, testing_tools.TestUserId, got.Id) - if err != nil { - return - } - - validateCreatedKey(t, tc.expectedResponse, setup_keys.ToResponseBody(key)) + // Verify setup key exists in DB via gorm + db := testing_tools.GetDB(t, am.GetStore()) + dbKey := testing_tools.VerifySetupKeyInDB(t, db, gotID) + assert.Equal(t, tc.expectedResponse.Name, dbKey.Name) + assert.Equal(t, tc.expectedResponse.Revoked, dbKey.Revoked) + assert.Equal(t, tc.expectedResponse.UsageLimit, dbKey.UsageLimit) select { case <-done: @@ -571,7 +570,7 @@ func Test_SetupKeys_Update(t *testing.T) { for _, tc := range tt { for _, user := range users { - t.Run(tc.name, func(t *testing.T) { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { apiHandler, am, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/setup_keys.sql", nil, true) body, err := json.Marshal(tc.requestBody) @@ -594,14 +593,16 @@ func Test_SetupKeys_Update(t *testing.T) { t.Fatalf("Sent content is not in correct json format; %v", err) } + gotID := got.Id + gotRevoked := got.Revoked + gotUsageLimit := got.UsageLimit validateCreatedKey(t, tc.expectedResponse, got) - key, err := am.GetSetupKey(context.Background(), testing_tools.TestAccountId, testing_tools.TestUserId, got.Id) - if err != nil { - return - } - - validateCreatedKey(t, tc.expectedResponse, setup_keys.ToResponseBody(key)) + // Verify updated setup key in DB via gorm + db := testing_tools.GetDB(t, am.GetStore()) + dbKey := testing_tools.VerifySetupKeyInDB(t, db, gotID) + assert.Equal(t, gotRevoked, dbKey.Revoked) + assert.Equal(t, gotUsageLimit, dbKey.UsageLimit) select { case <-done: @@ -759,8 +760,8 @@ func Test_SetupKeys_Get(t *testing.T) { apiHandler.ServeHTTP(recorder, req) - content, expectRespnose := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) - if !expectRespnose { + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { return } got := &api.SetupKey{} @@ -768,14 +769,16 @@ func Test_SetupKeys_Get(t *testing.T) { t.Fatalf("Sent content is not in correct json format; %v", err) } + gotID := got.Id + gotName := got.Name + gotRevoked := got.Revoked validateCreatedKey(t, tc.expectedResponse, got) - key, err := am.GetSetupKey(context.Background(), testing_tools.TestAccountId, testing_tools.TestUserId, got.Id) - if err != nil { - return - } - - validateCreatedKey(t, tc.expectedResponse, setup_keys.ToResponseBody(key)) + // Verify setup key in DB via gorm + db := testing_tools.GetDB(t, am.GetStore()) + dbKey := testing_tools.VerifySetupKeyInDB(t, db, gotID) + assert.Equal(t, gotName, dbKey.Name) + assert.Equal(t, gotRevoked, dbKey.Revoked) select { case <-done: @@ -928,15 +931,17 @@ func Test_SetupKeys_GetAll(t *testing.T) { return tc.expectedResponse[i].UsageLimit < tc.expectedResponse[j].UsageLimit }) + db := testing_tools.GetDB(t, am.GetStore()) for i := range tc.expectedResponse { + gotID := got[i].Id + gotName := got[i].Name + gotRevoked := got[i].Revoked validateCreatedKey(t, tc.expectedResponse[i], &got[i]) - key, err := am.GetSetupKey(context.Background(), testing_tools.TestAccountId, testing_tools.TestUserId, got[i].Id) - if err != nil { - return - } - - validateCreatedKey(t, tc.expectedResponse[i], setup_keys.ToResponseBody(key)) + // Verify each setup key in DB via gorm + dbKey := testing_tools.VerifySetupKeyInDB(t, db, gotID) + assert.Equal(t, gotName, dbKey.Name) + assert.Equal(t, gotRevoked, dbKey.Revoked) } select { @@ -1104,8 +1109,9 @@ func Test_SetupKeys_Delete(t *testing.T) { t.Fatalf("Sent content is not in correct json format; %v", err) } - _, err := am.GetSetupKey(context.Background(), testing_tools.TestAccountId, testing_tools.TestUserId, got.Id) - assert.Errorf(t, err, "Expected error when trying to get deleted key") + // Verify setup key deleted from DB via gorm + db := testing_tools.GetDB(t, am.GetStore()) + testing_tools.VerifySetupKeyNotInDB(t, db, got.Id) select { case <-done: @@ -1120,7 +1126,7 @@ func Test_SetupKeys_Delete(t *testing.T) { func validateCreatedKey(t *testing.T, expectedKey *api.SetupKey, got *api.SetupKey) { t.Helper() - if got.Expires.After(time.Now().Add(-1*time.Minute)) && got.Expires.Before(time.Now().Add(testing_tools.ExpiresIn*time.Second)) || + if (got.Expires.After(time.Now().Add(-1*time.Minute)) && got.Expires.Before(time.Now().Add(testing_tools.ExpiresIn*time.Second))) || got.Expires.After(time.Date(2300, 01, 01, 0, 0, 0, 0, time.Local)) || got.Expires.Before(time.Date(1950, 01, 01, 0, 0, 0, 0, time.Local)) { got.Expires = time.Time{} diff --git a/management/server/http/testing/integration/users_handler_integration_test.go b/management/server/http/testing/integration/users_handler_integration_test.go new file mode 100644 index 000000000..eae3b4ad5 --- /dev/null +++ b/management/server/http/testing/integration/users_handler_integration_test.go @@ -0,0 +1,701 @@ +//go:build integration + +package integration + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/netbirdio/netbird/management/server/http/testing/testing_tools" + "github.com/netbirdio/netbird/management/server/http/testing/testing_tools/channel" + "github.com/netbirdio/netbird/shared/management/http/api" +) + +func Test_Users_GetAll(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, true}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, true}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + for _, user := range users { + t.Run(user.name+" - Get all users", func(t *testing.T) { + apiHandler, _, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/users_integration.sql", nil, true) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, "/api/users", user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, http.StatusOK, user.expectResponse) + if !expectResponse { + return + } + + got := []api.User{} + if err := json.Unmarshal(content, &got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + + assert.GreaterOrEqual(t, len(got), 1) + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } +} + +func Test_Users_GetAll_ServiceUsers(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + for _, user := range users { + t.Run(user.name+" - Get all service users", func(t *testing.T) { + apiHandler, _, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/users_integration.sql", nil, true) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, "/api/users?service_user=true", user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, http.StatusOK, user.expectResponse) + if !expectResponse { + return + } + + got := []api.User{} + if err := json.Unmarshal(content, &got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + + for _, u := range got { + assert.NotNil(t, u.IsServiceUser) + assert.Equal(t, true, *u.IsServiceUser) + } + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } +} + +func Test_Users_Create_ServiceUser(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + tt := []struct { + name string + requestBody *api.UserCreateRequest + expectedStatus int + verifyResponse func(t *testing.T, user *api.User) + }{ + { + name: "Create service user with admin role", + requestBody: &api.UserCreateRequest{ + Role: "admin", + IsServiceUser: true, + AutoGroups: []string{testing_tools.TestGroupId}, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, user *api.User) { + t.Helper() + assert.NotEmpty(t, user.Id) + assert.Equal(t, "admin", user.Role) + assert.NotNil(t, user.IsServiceUser) + assert.Equal(t, true, *user.IsServiceUser) + }, + }, + { + name: "Create service user with user role", + requestBody: &api.UserCreateRequest{ + Role: "user", + IsServiceUser: true, + AutoGroups: []string{testing_tools.TestGroupId}, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, user *api.User) { + t.Helper() + assert.NotEmpty(t, user.Id) + assert.Equal(t, "user", user.Role) + }, + }, + { + name: "Create service user with empty auto_groups", + requestBody: &api.UserCreateRequest{ + Role: "admin", + IsServiceUser: true, + AutoGroups: []string{}, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, user *api.User) { + t.Helper() + assert.NotEmpty(t, user.Id) + }, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, am, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/users_integration.sql", nil, true) + + body, err := json.Marshal(tc.requestBody) + if err != nil { + t.Fatalf("Failed to marshal request body: %v", err) + } + + req := testing_tools.BuildRequest(t, body, http.MethodPost, "/api/users", user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.verifyResponse != nil { + got := &api.User{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + tc.verifyResponse(t, got) + + // Verify user in DB + db := testing_tools.GetDB(t, am.GetStore()) + dbUser := testing_tools.VerifyUserInDB(t, db, got.Id) + assert.True(t, dbUser.IsServiceUser) + assert.Equal(t, string(dbUser.Role), string(tc.requestBody.Role)) + } + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } + } +} + +func Test_Users_Update(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + tt := []struct { + name string + targetUserId string + requestBody *api.UserRequest + expectedStatus int + verifyResponse func(t *testing.T, user *api.User) + }{ + { + name: "Update user role to admin", + targetUserId: testing_tools.TestUserId, + requestBody: &api.UserRequest{ + Role: "admin", + AutoGroups: []string{}, + IsBlocked: false, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, user *api.User) { + t.Helper() + assert.Equal(t, "admin", user.Role) + }, + }, + { + name: "Update user auto_groups", + targetUserId: testing_tools.TestUserId, + requestBody: &api.UserRequest{ + Role: "user", + AutoGroups: []string{testing_tools.TestGroupId}, + IsBlocked: false, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, user *api.User) { + t.Helper() + assert.Equal(t, 1, len(user.AutoGroups)) + }, + }, + { + name: "Block user", + targetUserId: testing_tools.TestUserId, + requestBody: &api.UserRequest{ + Role: "user", + AutoGroups: []string{}, + IsBlocked: true, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, user *api.User) { + t.Helper() + assert.Equal(t, true, user.IsBlocked) + }, + }, + { + name: "Update non-existing user", + targetUserId: "nonExistingUserId", + requestBody: &api.UserRequest{ + Role: "user", + AutoGroups: []string{}, + IsBlocked: false, + }, + expectedStatus: http.StatusNotFound, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, am, _ := channel.BuildApiBlackBoxWithDBState(t, "../testdata/users_integration.sql", nil, false) + + body, err := json.Marshal(tc.requestBody) + if err != nil { + t.Fatalf("Failed to marshal request body: %v", err) + } + + req := testing_tools.BuildRequest(t, body, http.MethodPut, strings.Replace("/api/users/{userId}", "{userId}", tc.targetUserId, 1), user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.verifyResponse != nil { + got := &api.User{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + tc.verifyResponse(t, got) + + // Verify updated fields in DB + if tc.expectedStatus == http.StatusOK { + db := testing_tools.GetDB(t, am.GetStore()) + dbUser := testing_tools.VerifyUserInDB(t, db, tc.targetUserId) + assert.Equal(t, string(dbUser.Role), string(tc.requestBody.Role)) + assert.Equal(t, dbUser.Blocked, tc.requestBody.IsBlocked) + assert.ElementsMatch(t, dbUser.AutoGroups, tc.requestBody.AutoGroups) + } + } + }) + } + } +} + +func Test_Users_Delete(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + tt := []struct { + name string + targetUserId string + expectedStatus int + }{ + { + name: "Delete existing service user", + targetUserId: "deletableServiceUserId", + expectedStatus: http.StatusOK, + }, + { + name: "Delete non-existing user", + targetUserId: "nonExistingUserId", + expectedStatus: http.StatusNotFound, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, am, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/users_integration.sql", nil, true) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodDelete, strings.Replace("/api/users/{userId}", "{userId}", tc.targetUserId, 1), user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + _, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + + // Verify user deleted from DB for successful deletes + if expectResponse && tc.expectedStatus == http.StatusOK { + db := testing_tools.GetDB(t, am.GetStore()) + testing_tools.VerifyUserNotInDB(t, db, tc.targetUserId) + } + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } + } +} + +func Test_PATs_GetAll(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + for _, user := range users { + t.Run(user.name+" - Get all PATs for service user", func(t *testing.T) { + apiHandler, _, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/users_integration.sql", nil, true) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, strings.Replace("/api/users/{userId}/tokens", "{userId}", testing_tools.TestServiceUserId, 1), user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, http.StatusOK, user.expectResponse) + if !expectResponse { + return + } + + got := []api.PersonalAccessToken{} + if err := json.Unmarshal(content, &got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + + assert.Equal(t, 1, len(got)) + assert.Equal(t, "serviceToken", got[0].Name) + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } +} + +func Test_PATs_GetById(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + tt := []struct { + name string + tokenId string + expectedStatus int + expectToken bool + }{ + { + name: "Get existing PAT", + tokenId: "serviceTokenId", + expectedStatus: http.StatusOK, + expectToken: true, + }, + { + name: "Get non-existing PAT", + tokenId: "nonExistingTokenId", + expectedStatus: http.StatusNotFound, + expectToken: false, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, _, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/users_integration.sql", nil, true) + + path := strings.Replace("/api/users/{userId}/tokens/{tokenId}", "{userId}", testing_tools.TestServiceUserId, 1) + path = strings.Replace(path, "{tokenId}", tc.tokenId, 1) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodGet, path, user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.expectToken { + got := &api.PersonalAccessToken{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + assert.Equal(t, "serviceTokenId", got.Id) + assert.Equal(t, "serviceToken", got.Name) + } + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } + } +} + +func Test_PATs_Create(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + tt := []struct { + name string + targetUserId string + requestBody *api.PersonalAccessTokenRequest + expectedStatus int + verifyResponse func(t *testing.T, pat *api.PersonalAccessTokenGenerated) + }{ + { + name: "Create PAT with 30 day expiry", + targetUserId: testing_tools.TestServiceUserId, + requestBody: &api.PersonalAccessTokenRequest{ + Name: "newPAT", + ExpiresIn: 30, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, pat *api.PersonalAccessTokenGenerated) { + t.Helper() + assert.NotEmpty(t, pat.PlainToken) + assert.Equal(t, "newPAT", pat.PersonalAccessToken.Name) + }, + }, + { + name: "Create PAT with 365 day expiry", + targetUserId: testing_tools.TestServiceUserId, + requestBody: &api.PersonalAccessTokenRequest{ + Name: "longPAT", + ExpiresIn: 365, + }, + expectedStatus: http.StatusOK, + verifyResponse: func(t *testing.T, pat *api.PersonalAccessTokenGenerated) { + t.Helper() + assert.NotEmpty(t, pat.PlainToken) + assert.Equal(t, "longPAT", pat.PersonalAccessToken.Name) + }, + }, + { + name: "Create PAT with empty name", + targetUserId: testing_tools.TestServiceUserId, + requestBody: &api.PersonalAccessTokenRequest{ + Name: "", + ExpiresIn: 30, + }, + expectedStatus: http.StatusUnprocessableEntity, + }, + { + name: "Create PAT with 0 day expiry", + targetUserId: testing_tools.TestServiceUserId, + requestBody: &api.PersonalAccessTokenRequest{ + Name: "zeroPAT", + ExpiresIn: 0, + }, + expectedStatus: http.StatusUnprocessableEntity, + }, + { + name: "Create PAT with expiry over 365 days", + targetUserId: testing_tools.TestServiceUserId, + requestBody: &api.PersonalAccessTokenRequest{ + Name: "tooLongPAT", + ExpiresIn: 400, + }, + expectedStatus: http.StatusUnprocessableEntity, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, am, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/users_integration.sql", nil, true) + + body, err := json.Marshal(tc.requestBody) + if err != nil { + t.Fatalf("Failed to marshal request body: %v", err) + } + + req := testing_tools.BuildRequest(t, body, http.MethodPost, strings.Replace("/api/users/{userId}/tokens", "{userId}", tc.targetUserId, 1), user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + content, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + if !expectResponse { + return + } + + if tc.verifyResponse != nil { + got := &api.PersonalAccessTokenGenerated{} + if err := json.Unmarshal(content, got); err != nil { + t.Fatalf("Sent content is not in correct json format; %v", err) + } + tc.verifyResponse(t, got) + + // Verify PAT in DB + db := testing_tools.GetDB(t, am.GetStore()) + dbPAT := testing_tools.VerifyPATInDB(t, db, got.PersonalAccessToken.Id) + assert.Equal(t, tc.requestBody.Name, dbPAT.Name) + } + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } + } +} + +func Test_PATs_Delete(t *testing.T) { + users := []struct { + name string + userId string + expectResponse bool + }{ + {"Regular user", testing_tools.TestUserId, false}, + {"Admin user", testing_tools.TestAdminId, true}, + {"Owner user", testing_tools.TestOwnerId, true}, + {"Regular service user", testing_tools.TestServiceUserId, false}, + {"Admin service user", testing_tools.TestServiceAdminId, true}, + {"Blocked user", testing_tools.BlockedUserId, false}, + {"Other user", testing_tools.OtherUserId, false}, + {"Invalid token", testing_tools.InvalidToken, false}, + } + + tt := []struct { + name string + tokenId string + expectedStatus int + }{ + { + name: "Delete existing PAT", + tokenId: "serviceTokenId", + expectedStatus: http.StatusOK, + }, + { + name: "Delete non-existing PAT", + tokenId: "nonExistingTokenId", + expectedStatus: http.StatusNotFound, + }, + } + + for _, tc := range tt { + for _, user := range users { + t.Run(user.name+" - "+tc.name, func(t *testing.T) { + apiHandler, am, done := channel.BuildApiBlackBoxWithDBState(t, "../testdata/users_integration.sql", nil, true) + + path := strings.Replace("/api/users/{userId}/tokens/{tokenId}", "{userId}", testing_tools.TestServiceUserId, 1) + path = strings.Replace(path, "{tokenId}", tc.tokenId, 1) + + req := testing_tools.BuildRequest(t, []byte{}, http.MethodDelete, path, user.userId) + recorder := httptest.NewRecorder() + apiHandler.ServeHTTP(recorder, req) + + _, expectResponse := testing_tools.ReadResponse(t, recorder, tc.expectedStatus, user.expectResponse) + + // Verify PAT deleted from DB for successful deletes + if expectResponse && tc.expectedStatus == http.StatusOK { + db := testing_tools.GetDB(t, am.GetStore()) + testing_tools.VerifyPATNotInDB(t, db, tc.tokenId) + } + + select { + case <-done: + case <-time.After(time.Second): + t.Error("timeout waiting for peerShouldNotReceiveUpdate") + } + }) + } + } +} diff --git a/management/server/http/testing/testdata/accounts.sql b/management/server/http/testing/testdata/accounts.sql new file mode 100644 index 000000000..35f00d419 --- /dev/null +++ b/management/server/http/testing/testdata/accounts.sql @@ -0,0 +1,18 @@ +CREATE TABLE `accounts` (`id` text,`created_by` text,`created_at` datetime,`domain` text,`domain_category` text,`is_domain_primary_account` numeric,`network_identifier` text,`network_net` text,`network_dns` text,`network_serial` integer,`dns_settings_disabled_management_groups` text,`settings_peer_login_expiration_enabled` numeric,`settings_peer_login_expiration` integer,`settings_regular_users_view_blocked` numeric,`settings_groups_propagation_enabled` numeric,`settings_jwt_groups_enabled` numeric,`settings_jwt_groups_claim_name` text,`settings_jwt_allow_groups` text,`settings_extra_peer_approval_enabled` numeric,`settings_extra_integrated_validator_groups` text,PRIMARY KEY (`id`)); +CREATE TABLE `users` (`id` text,`account_id` text,`role` text,`is_service_user` numeric,`non_deletable` numeric,`service_user_name` text,`auto_groups` text,`blocked` numeric,`last_login` datetime DEFAULT NULL,`created_at` datetime,`issued` text DEFAULT "api",`integration_ref_id` integer,`integration_ref_integration_type` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_users_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `groups` (`id` text,`account_id` text,`name` text,`issued` text,`peers` text,`integration_ref_id` integer,`integration_ref_integration_type` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_groups_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `peers` (`id` text,`account_id` text,`key` text,`setup_key` text,`ip` text,`meta_hostname` text,`meta_go_os` text,`meta_kernel` text,`meta_core` text,`meta_platform` text,`meta_os` text,`meta_os_version` text,`meta_wt_version` text,`meta_ui_version` text,`meta_kernel_version` text,`meta_network_addresses` text,`meta_system_serial_number` text,`meta_system_product_name` text,`meta_system_manufacturer` text,`meta_environment` text,`meta_files` text,`name` text,`dns_label` text,`peer_status_last_seen` datetime,`peer_status_connected` numeric,`peer_status_login_expired` numeric,`peer_status_requires_approval` numeric,`user_id` text,`ssh_key` text,`ssh_enabled` numeric,`login_expiration_enabled` numeric,`last_login` datetime,`created_at` datetime,`ephemeral` numeric,`location_connection_ip` text,`location_country_code` text,`location_city_name` text,`location_geo_name_id` integer,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_peers_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `setup_keys` (`id` text,`account_id` text,`key` text,`key_secret` text,`name` text,`type` text,`created_at` datetime,`expires_at` datetime,`updated_at` datetime,`revoked` numeric,`used_times` integer,`last_used` datetime DEFAULT NULL,`auto_groups` text,`usage_limit` integer,`ephemeral` numeric,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_setup_keys_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); + +INSERT INTO accounts VALUES('testAccountId','','2024-10-02 16:01:38.000000000+00:00','test.com','private',1,'testNetworkIdentifier','{"IP":"100.64.0.0","Mask":"//8AAA=="}','',0,'[]',1,86400000000000,0,0,0,'',NULL,NULL,NULL); +INSERT INTO users VALUES('testUserId','testAccountId','user',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testAdminId','testAccountId','admin',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testOwnerId','testAccountId','owner',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testServiceUserId','testAccountId','user',1,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testServiceAdminId','testAccountId','admin',1,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('blockedUserId','testAccountId','admin',0,0,'','[]',1,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('otherUserId','otherAccountId','admin',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO "groups" VALUES('testGroupId','testAccountId','testGroupName','api','[]',0,''); +INSERT INTO "groups" VALUES('newGroupId','testAccountId','newGroupName','api','[]',0,''); +INSERT INTO peers VALUES('testPeerId','testAccountId','5rvhvriKJZ3S9oxYToVj5TzDM9u9y8cxg7htIMWlYAg=','72546A29-6BC8-4311-BCFC-9CDBF33F1A48','"100.64.114.31"','f2a34f6a4731','linux','Linux','11','unknown','Debian GNU/Linux','','0.12.0','','',NULL,'','','','{"Cloud":"","Platform":""}',NULL,'f2a34f6a4731','f2a34f6a4731','2023-03-02 09:21:02.189035775+01:00',0,0,0,'','ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILzUUSYG/LGnV8zarb2SGN+tib/PZ+M7cL4WtTzUrTpk',0,1,'2023-03-01 19:48:19.817799698+01:00','2024-10-02 17:00:32.527947+02:00',0,'""','','',0); +INSERT INTO setup_keys VALUES('testKeyId','testAccountId','testKey','testK****','existingKey','one-off','2021-08-19 20:46:20.000000000+00:00','2321-09-18 20:46:20.000000000+00:00','2021-08-19 20:46:20.000000000+00:000',0,0,NULL,'["testGroupId"]',1,0); diff --git a/management/server/http/testing/testdata/dns.sql b/management/server/http/testing/testdata/dns.sql new file mode 100644 index 000000000..9ed4daf7e --- /dev/null +++ b/management/server/http/testing/testdata/dns.sql @@ -0,0 +1,21 @@ +CREATE TABLE `accounts` (`id` text,`created_by` text,`created_at` datetime,`domain` text,`domain_category` text,`is_domain_primary_account` numeric,`network_identifier` text,`network_net` text,`network_dns` text,`network_serial` integer,`dns_settings_disabled_management_groups` text,`settings_peer_login_expiration_enabled` numeric,`settings_peer_login_expiration` integer,`settings_regular_users_view_blocked` numeric,`settings_groups_propagation_enabled` numeric,`settings_jwt_groups_enabled` numeric,`settings_jwt_groups_claim_name` text,`settings_jwt_allow_groups` text,`settings_extra_peer_approval_enabled` numeric,`settings_extra_integrated_validator_groups` text,PRIMARY KEY (`id`)); +CREATE TABLE `users` (`id` text,`account_id` text,`role` text,`is_service_user` numeric,`non_deletable` numeric,`service_user_name` text,`auto_groups` text,`blocked` numeric,`last_login` datetime DEFAULT NULL,`created_at` datetime,`issued` text DEFAULT "api",`integration_ref_id` integer,`integration_ref_integration_type` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_users_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `groups` (`id` text,`account_id` text,`name` text,`issued` text,`peers` text,`integration_ref_id` integer,`integration_ref_integration_type` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_groups_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `setup_keys` (`id` text,`account_id` text,`key` text,`key_secret` text,`name` text,`type` text,`created_at` datetime,`expires_at` datetime,`updated_at` datetime,`revoked` numeric,`used_times` integer,`last_used` datetime DEFAULT NULL,`auto_groups` text,`usage_limit` integer,`ephemeral` numeric,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_setup_keys_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `peers` (`id` text,`account_id` text,`key` text,`setup_key` text,`ip` text,`meta_hostname` text,`meta_go_os` text,`meta_kernel` text,`meta_core` text,`meta_platform` text,`meta_os` text,`meta_os_version` text,`meta_wt_version` text,`meta_ui_version` text,`meta_kernel_version` text,`meta_network_addresses` text,`meta_system_serial_number` text,`meta_system_product_name` text,`meta_system_manufacturer` text,`meta_environment` text,`meta_files` text,`name` text,`dns_label` text,`peer_status_last_seen` datetime,`peer_status_connected` numeric,`peer_status_login_expired` numeric,`peer_status_requires_approval` numeric,`user_id` text,`ssh_key` text,`ssh_enabled` numeric,`login_expiration_enabled` numeric,`last_login` datetime,`created_at` datetime,`ephemeral` numeric,`location_connection_ip` text,`location_country_code` text,`location_city_name` text,`location_geo_name_id` integer,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_peers_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `name_server_groups` (`id` text,`account_id` text,`name` text,`description` text,`name_servers` text,`groups` text,`primary` numeric,`domains` text,`enabled` numeric,`search_domains_enabled` numeric,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_name_server_groups_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); + +INSERT INTO accounts VALUES('testAccountId','','2024-10-02 16:01:38.000000000+00:00','test.com','private',1,'testNetworkIdentifier','{"IP":"100.64.0.0","Mask":"//8AAA=="}','',0,'[]',1,86400000000000,0,0,0,'',NULL,NULL,NULL); +INSERT INTO users VALUES('testUserId','testAccountId','user',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testAdminId','testAccountId','admin',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testOwnerId','testAccountId','owner',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testServiceUserId','testAccountId','user',1,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testServiceAdminId','testAccountId','admin',1,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('blockedUserId','testAccountId','admin',0,0,'','[]',1,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('otherUserId','otherAccountId','admin',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO "groups" VALUES('testGroupId','testAccountId','testGroupName','api','["testPeerId"]',0,''); +INSERT INTO "groups" VALUES('newGroupId','testAccountId','newGroupName','api','[]',0,''); +INSERT INTO setup_keys VALUES('testKeyId','testAccountId','testKey','testK****','existingKey','one-off','2021-08-19 20:46:20.000000000+00:00','2321-09-18 20:46:20.000000000+00:00','2021-08-19 20:46:20.000000000+00:000',0,0,NULL,'["testGroupId"]',1,0); +INSERT INTO peers VALUES('testPeerId','testAccountId','5rvhvriKJZ3S9oxYToVj5TzDM9u9y8cxg7htIMWlYAg=','72546A29-6BC8-4311-BCFC-9CDBF33F1A48','"100.64.114.31"','f2a34f6a4731','linux','Linux','11','unknown','Debian GNU/Linux','','0.12.0','','',NULL,'','','','{"Cloud":"","Platform":""}',NULL,'f2a34f6a4731','f2a34f6a4731','2023-03-02 09:21:02.189035775+01:00',0,0,0,'','ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILzUUSYG/LGnV8zarb2SGN+tib/PZ+M7cL4WtTzUrTpk',0,1,'2023-03-01 19:48:19.817799698+01:00','2024-10-02 17:00:32.527947+02:00',0,'""','','',0); + +INSERT INTO name_server_groups VALUES('testNSGroupId','testAccountId','testNSGroup','test nameserver group','[{"IP":"1.1.1.1","NSType":1,"Port":53}]','["testGroupId"]',0,'["example.com"]',1,0); \ No newline at end of file diff --git a/management/server/http/testing/testdata/events.sql b/management/server/http/testing/testdata/events.sql new file mode 100644 index 000000000..27fd01aea --- /dev/null +++ b/management/server/http/testing/testdata/events.sql @@ -0,0 +1,18 @@ +CREATE TABLE `accounts` (`id` text,`created_by` text,`created_at` datetime,`domain` text,`domain_category` text,`is_domain_primary_account` numeric,`network_identifier` text,`network_net` text,`network_dns` text,`network_serial` integer,`dns_settings_disabled_management_groups` text,`settings_peer_login_expiration_enabled` numeric,`settings_peer_login_expiration` integer,`settings_regular_users_view_blocked` numeric,`settings_groups_propagation_enabled` numeric,`settings_jwt_groups_enabled` numeric,`settings_jwt_groups_claim_name` text,`settings_jwt_allow_groups` text,`settings_extra_peer_approval_enabled` numeric,`settings_extra_integrated_validator_groups` text,PRIMARY KEY (`id`)); +CREATE TABLE `users` (`id` text,`account_id` text,`role` text,`is_service_user` numeric,`non_deletable` numeric,`service_user_name` text,`auto_groups` text,`blocked` numeric,`last_login` datetime DEFAULT NULL,`created_at` datetime,`issued` text DEFAULT "api",`integration_ref_id` integer,`integration_ref_integration_type` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_users_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `groups` (`id` text,`account_id` text,`name` text,`issued` text,`peers` text,`integration_ref_id` integer,`integration_ref_integration_type` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_groups_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `setup_keys` (`id` text,`account_id` text,`key` text,`key_secret` text,`name` text,`type` text,`created_at` datetime,`expires_at` datetime,`updated_at` datetime,`revoked` numeric,`used_times` integer,`last_used` datetime DEFAULT NULL,`auto_groups` text,`usage_limit` integer,`ephemeral` numeric,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_setup_keys_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `peers` (`id` text,`account_id` text,`key` text,`setup_key` text,`ip` text,`meta_hostname` text,`meta_go_os` text,`meta_kernel` text,`meta_core` text,`meta_platform` text,`meta_os` text,`meta_os_version` text,`meta_wt_version` text,`meta_ui_version` text,`meta_kernel_version` text,`meta_network_addresses` text,`meta_system_serial_number` text,`meta_system_product_name` text,`meta_system_manufacturer` text,`meta_environment` text,`meta_files` text,`name` text,`dns_label` text,`peer_status_last_seen` datetime,`peer_status_connected` numeric,`peer_status_login_expired` numeric,`peer_status_requires_approval` numeric,`user_id` text,`ssh_key` text,`ssh_enabled` numeric,`login_expiration_enabled` numeric,`last_login` datetime,`created_at` datetime,`ephemeral` numeric,`location_connection_ip` text,`location_country_code` text,`location_city_name` text,`location_geo_name_id` integer,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_peers_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); + +INSERT INTO accounts VALUES('testAccountId','','2024-10-02 16:01:38.000000000+00:00','test.com','private',1,'testNetworkIdentifier','{"IP":"100.64.0.0","Mask":"//8AAA=="}','',0,'[]',1,86400000000000,0,0,0,'',NULL,NULL,NULL); +INSERT INTO users VALUES('testUserId','testAccountId','user',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testAdminId','testAccountId','admin',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testOwnerId','testAccountId','owner',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testServiceUserId','testAccountId','user',1,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testServiceAdminId','testAccountId','admin',1,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('blockedUserId','testAccountId','admin',0,0,'','[]',1,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('otherUserId','otherAccountId','admin',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO "groups" VALUES('testGroupId','testAccountId','testGroupName','api','[]',0,''); +INSERT INTO "groups" VALUES('newGroupId','testAccountId','newGroupName','api','[]',0,''); +INSERT INTO setup_keys VALUES('testKeyId','testAccountId','testKey','testK****','existingKey','one-off','2021-08-19 20:46:20.000000000+00:00','2321-09-18 20:46:20.000000000+00:00','2021-08-19 20:46:20.000000000+00:000',0,0,NULL,'["testGroupId"]',1,0); +INSERT INTO peers VALUES('testPeerId','testAccountId','5rvhvriKJZ3S9oxYToVj5TzDM9u9y8cxg7htIMWlYAg=','72546A29-6BC8-4311-BCFC-9CDBF33F1A48','"100.64.114.31"','f2a34f6a4731','linux','Linux','11','unknown','Debian GNU/Linux','','0.12.0','','',NULL,'','','','{"Cloud":"","Platform":""}',NULL,'f2a34f6a4731','f2a34f6a4731','2023-03-02 09:21:02.189035775+01:00',0,0,0,'','ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILzUUSYG/LGnV8zarb2SGN+tib/PZ+M7cL4WtTzUrTpk',0,1,'2023-03-01 19:48:19.817799698+01:00','2024-10-02 17:00:32.527947+02:00',0,'""','','',0); \ No newline at end of file diff --git a/management/server/http/testing/testdata/groups.sql b/management/server/http/testing/testdata/groups.sql new file mode 100644 index 000000000..eb874f036 --- /dev/null +++ b/management/server/http/testing/testdata/groups.sql @@ -0,0 +1,19 @@ +CREATE TABLE `accounts` (`id` text,`created_by` text,`created_at` datetime,`domain` text,`domain_category` text,`is_domain_primary_account` numeric,`network_identifier` text,`network_net` text,`network_dns` text,`network_serial` integer,`dns_settings_disabled_management_groups` text,`settings_peer_login_expiration_enabled` numeric,`settings_peer_login_expiration` integer,`settings_regular_users_view_blocked` numeric,`settings_groups_propagation_enabled` numeric,`settings_jwt_groups_enabled` numeric,`settings_jwt_groups_claim_name` text,`settings_jwt_allow_groups` text,`settings_extra_peer_approval_enabled` numeric,`settings_extra_integrated_validator_groups` text,PRIMARY KEY (`id`)); +CREATE TABLE `users` (`id` text,`account_id` text,`role` text,`is_service_user` numeric,`non_deletable` numeric,`service_user_name` text,`auto_groups` text,`blocked` numeric,`last_login` datetime DEFAULT NULL,`created_at` datetime,`issued` text DEFAULT "api",`integration_ref_id` integer,`integration_ref_integration_type` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_users_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `groups` (`id` text,`account_id` text,`name` text,`issued` text,`peers` text,`integration_ref_id` integer,`integration_ref_integration_type` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_groups_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `setup_keys` (`id` text,`account_id` text,`key` text,`key_secret` text,`name` text,`type` text,`created_at` datetime,`expires_at` datetime,`updated_at` datetime,`revoked` numeric,`used_times` integer,`last_used` datetime DEFAULT NULL,`auto_groups` text,`usage_limit` integer,`ephemeral` numeric,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_setup_keys_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `peers` (`id` text,`account_id` text,`key` text,`setup_key` text,`ip` text,`meta_hostname` text,`meta_go_os` text,`meta_kernel` text,`meta_core` text,`meta_platform` text,`meta_os` text,`meta_os_version` text,`meta_wt_version` text,`meta_ui_version` text,`meta_kernel_version` text,`meta_network_addresses` text,`meta_system_serial_number` text,`meta_system_product_name` text,`meta_system_manufacturer` text,`meta_environment` text,`meta_files` text,`name` text,`dns_label` text,`peer_status_last_seen` datetime,`peer_status_connected` numeric,`peer_status_login_expired` numeric,`peer_status_requires_approval` numeric,`user_id` text,`ssh_key` text,`ssh_enabled` numeric,`login_expiration_enabled` numeric,`last_login` datetime,`created_at` datetime,`ephemeral` numeric,`location_connection_ip` text,`location_country_code` text,`location_city_name` text,`location_geo_name_id` integer,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_peers_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); + +INSERT INTO accounts VALUES('testAccountId','','2024-10-02 16:01:38.000000000+00:00','test.com','private',1,'testNetworkIdentifier','{"IP":"100.64.0.0","Mask":"//8AAA=="}','',0,'[]',1,86400000000000,0,0,0,'',NULL,NULL,NULL); +INSERT INTO users VALUES('testUserId','testAccountId','user',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testAdminId','testAccountId','admin',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testOwnerId','testAccountId','owner',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testServiceUserId','testAccountId','user',1,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testServiceAdminId','testAccountId','admin',1,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('blockedUserId','testAccountId','admin',0,0,'','[]',1,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('otherUserId','otherAccountId','admin',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO "groups" VALUES('testGroupId','testAccountId','testGroupName','api','["testPeerId"]',0,''); +INSERT INTO "groups" VALUES('newGroupId','testAccountId','newGroupName','api','[]',0,''); +INSERT INTO "groups" VALUES('allGroupId','testAccountId','All','api','[]',0,''); +INSERT INTO setup_keys VALUES('testKeyId','testAccountId','testKey','testK****','existingKey','one-off','2021-08-19 20:46:20.000000000+00:00','2321-09-18 20:46:20.000000000+00:00','2021-08-19 20:46:20.000000000+00:000',0,0,NULL,'["testGroupId"]',1,0); +INSERT INTO peers VALUES('testPeerId','testAccountId','5rvhvriKJZ3S9oxYToVj5TzDM9u9y8cxg7htIMWlYAg=','72546A29-6BC8-4311-BCFC-9CDBF33F1A48','"100.64.114.31"','f2a34f6a4731','linux','Linux','11','unknown','Debian GNU/Linux','','0.12.0','','',NULL,'','','','{"Cloud":"","Platform":""}',NULL,'f2a34f6a4731','f2a34f6a4731','2023-03-02 09:21:02.189035775+01:00',0,0,0,'','ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILzUUSYG/LGnV8zarb2SGN+tib/PZ+M7cL4WtTzUrTpk',0,1,'2023-03-01 19:48:19.817799698+01:00','2024-10-02 17:00:32.527947+02:00',0,'""','','',0); \ No newline at end of file diff --git a/management/server/http/testing/testdata/networks.sql b/management/server/http/testing/testdata/networks.sql new file mode 100644 index 000000000..39ec8e646 --- /dev/null +++ b/management/server/http/testing/testdata/networks.sql @@ -0,0 +1,25 @@ +CREATE TABLE `accounts` (`id` text,`created_by` text,`created_at` datetime,`domain` text,`domain_category` text,`is_domain_primary_account` numeric,`network_identifier` text,`network_net` text,`network_dns` text,`network_serial` integer,`dns_settings_disabled_management_groups` text,`settings_peer_login_expiration_enabled` numeric,`settings_peer_login_expiration` integer,`settings_regular_users_view_blocked` numeric,`settings_groups_propagation_enabled` numeric,`settings_jwt_groups_enabled` numeric,`settings_jwt_groups_claim_name` text,`settings_jwt_allow_groups` text,`settings_extra_peer_approval_enabled` numeric,`settings_extra_integrated_validator_groups` text,PRIMARY KEY (`id`)); +CREATE TABLE `users` (`id` text,`account_id` text,`role` text,`is_service_user` numeric,`non_deletable` numeric,`service_user_name` text,`auto_groups` text,`blocked` numeric,`last_login` datetime DEFAULT NULL,`created_at` datetime,`issued` text DEFAULT "api",`integration_ref_id` integer,`integration_ref_integration_type` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_users_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `groups` (`id` text,`account_id` text,`name` text,`issued` text,`peers` text,`integration_ref_id` integer,`integration_ref_integration_type` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_groups_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `setup_keys` (`id` text,`account_id` text,`key` text,`key_secret` text,`name` text,`type` text,`created_at` datetime,`expires_at` datetime,`updated_at` datetime,`revoked` numeric,`used_times` integer,`last_used` datetime DEFAULT NULL,`auto_groups` text,`usage_limit` integer,`ephemeral` numeric,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_setup_keys_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `peers` (`id` text,`account_id` text,`key` text,`setup_key` text,`ip` text,`meta_hostname` text,`meta_go_os` text,`meta_kernel` text,`meta_core` text,`meta_platform` text,`meta_os` text,`meta_os_version` text,`meta_wt_version` text,`meta_ui_version` text,`meta_kernel_version` text,`meta_network_addresses` text,`meta_system_serial_number` text,`meta_system_product_name` text,`meta_system_manufacturer` text,`meta_environment` text,`meta_files` text,`name` text,`dns_label` text,`peer_status_last_seen` datetime,`peer_status_connected` numeric,`peer_status_login_expired` numeric,`peer_status_requires_approval` numeric,`user_id` text,`ssh_key` text,`ssh_enabled` numeric,`login_expiration_enabled` numeric,`last_login` datetime,`created_at` datetime,`ephemeral` numeric,`location_connection_ip` text,`location_country_code` text,`location_city_name` text,`location_geo_name_id` integer,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_peers_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `networks` (`id` text,`account_id` text,`name` text,`description` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_networks` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `network_routers` (`id` text,`network_id` text,`account_id` text,`peer` text,`peer_groups` text,`masquerade` numeric,`metric` integer,`enabled` numeric,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_network_routers` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `network_resources` (`id` text,`network_id` text,`account_id` text,`name` text,`description` text,`type` text,`domain` text,`prefix` text,`enabled` numeric,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_network_resources` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); + +INSERT INTO accounts VALUES('testAccountId','','2024-10-02 16:01:38.000000000+00:00','test.com','private',1,'testNetworkIdentifier','{"IP":"100.64.0.0","Mask":"//8AAA=="}','',0,'[]',1,86400000000000,0,0,0,'',NULL,NULL,NULL); +INSERT INTO users VALUES('testUserId','testAccountId','user',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testAdminId','testAccountId','admin',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testOwnerId','testAccountId','owner',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testServiceUserId','testAccountId','user',1,0,'testServiceUser','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testServiceAdminId','testAccountId','admin',1,0,'testServiceAdmin','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('blockedUserId','testAccountId','admin',0,0,'','[]',1,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('otherUserId','otherAccountId','admin',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO "groups" VALUES('testGroupId','testAccountId','testGroupName','api','["testPeerId"]',0,''); +INSERT INTO "groups" VALUES('newGroupId','testAccountId','newGroupName','api','[]',0,''); +INSERT INTO setup_keys VALUES('testKeyId','testAccountId','testKey','testK****','existingKey','one-off','2021-08-19 20:46:20.000000000+00:00','2321-09-18 20:46:20.000000000+00:00','2021-08-19 20:46:20.000000000+00:00',0,0,NULL,'["testGroupId"]',1,0); +INSERT INTO peers VALUES('testPeerId','testAccountId','5rvhvriKJZ3S9oxYToVj5TzDM9u9y8cxg7htIMWlYAg=','72546A29-6BC8-4311-BCFC-9CDBF33F1A48','"100.64.114.31"','f2a34f6a4731','linux','Linux','11','unknown','Debian GNU/Linux','','0.12.0','','',NULL,'','','','{"Cloud":"","Platform":""}',NULL,'f2a34f6a4731','f2a34f6a4731','2023-03-02 09:21:02.189035775+01:00',0,0,0,'','ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILzUUSYG/LGnV8zarb2SGN+tib/PZ+M7cL4WtTzUrTpk',0,1,'2023-03-01 19:48:19.817799698+01:00','2024-10-02 17:00:32.527947+02:00',0,'""','','',0); + +INSERT INTO networks VALUES('testNetworkId','testAccountId','testNetwork','test network description'); +INSERT INTO network_routers VALUES('testRouterId','testNetworkId','testAccountId','testPeerId','[]',1,100,1); +INSERT INTO network_resources VALUES('testResourceId','testNetworkId','testAccountId','testResource','test resource description','host','','"3.3.3.3/32"',1); \ No newline at end of file diff --git a/management/server/http/testing/testdata/peers_integration.sql b/management/server/http/testing/testdata/peers_integration.sql new file mode 100644 index 000000000..62a7760e7 --- /dev/null +++ b/management/server/http/testing/testdata/peers_integration.sql @@ -0,0 +1,20 @@ +CREATE TABLE `accounts` (`id` text,`created_by` text,`created_at` datetime,`domain` text,`domain_category` text,`is_domain_primary_account` numeric,`network_identifier` text,`network_net` text,`network_dns` text,`network_serial` integer,`dns_settings_disabled_management_groups` text,`settings_peer_login_expiration_enabled` numeric,`settings_peer_login_expiration` integer,`settings_regular_users_view_blocked` numeric,`settings_groups_propagation_enabled` numeric,`settings_jwt_groups_enabled` numeric,`settings_jwt_groups_claim_name` text,`settings_jwt_allow_groups` text,`settings_extra_peer_approval_enabled` numeric,`settings_extra_integrated_validator_groups` text,PRIMARY KEY (`id`)); +CREATE TABLE `users` (`id` text,`account_id` text,`role` text,`is_service_user` numeric,`non_deletable` numeric,`service_user_name` text,`auto_groups` text,`blocked` numeric,`last_login` datetime DEFAULT NULL,`created_at` datetime,`issued` text DEFAULT "api",`integration_ref_id` integer,`integration_ref_integration_type` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_users_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `groups` (`id` text,`account_id` text,`name` text,`issued` text,`peers` text,`integration_ref_id` integer,`integration_ref_integration_type` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_groups_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `setup_keys` (`id` text,`account_id` text,`key` text,`key_secret` text,`name` text,`type` text,`created_at` datetime,`expires_at` datetime,`updated_at` datetime,`revoked` numeric,`used_times` integer,`last_used` datetime DEFAULT NULL,`auto_groups` text,`usage_limit` integer,`ephemeral` numeric,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_setup_keys_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `peers` (`id` text,`account_id` text,`key` text,`setup_key` text,`ip` text,`meta_hostname` text,`meta_go_os` text,`meta_kernel` text,`meta_core` text,`meta_platform` text,`meta_os` text,`meta_os_version` text,`meta_wt_version` text,`meta_ui_version` text,`meta_kernel_version` text,`meta_network_addresses` text,`meta_system_serial_number` text,`meta_system_product_name` text,`meta_system_manufacturer` text,`meta_environment` text,`meta_files` text,`name` text,`dns_label` text,`peer_status_last_seen` datetime,`peer_status_connected` numeric,`peer_status_login_expired` numeric,`peer_status_requires_approval` numeric,`user_id` text,`ssh_key` text,`ssh_enabled` numeric,`login_expiration_enabled` numeric,`last_login` datetime,`created_at` datetime,`ephemeral` numeric,`location_connection_ip` text,`location_country_code` text,`location_city_name` text,`location_geo_name_id` integer,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_peers_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); + +INSERT INTO accounts VALUES('testAccountId','','2024-10-02 16:01:38.000000000+00:00','test.com','private',1,'testNetworkIdentifier','{"IP":"100.64.0.0","Mask":"//8AAA=="}','',0,'[]',0,86400000000000,0,0,0,'',NULL,NULL,NULL); +INSERT INTO users VALUES('testUserId','testAccountId','user',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testAdminId','testAccountId','admin',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testOwnerId','testAccountId','owner',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testServiceUserId','testAccountId','user',1,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testServiceAdminId','testAccountId','admin',1,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('blockedUserId','testAccountId','admin',0,0,'','[]',1,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('otherUserId','otherAccountId','admin',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO "groups" VALUES('testGroupId','testAccountId','testGroupName','api','["testPeerId","testPeerId2"]',0,''); +INSERT INTO "groups" VALUES('newGroupId','testAccountId','newGroupName','api','[]',0,''); +INSERT INTO setup_keys VALUES('testKeyId','testAccountId','testKey','testK****','existingKey','one-off','2021-08-19 20:46:20.000000000+00:00','2321-09-18 20:46:20.000000000+00:00','2021-08-19 20:46:20.000000000+00:000',0,0,NULL,'["testGroupId"]',1,0); + +INSERT INTO peers VALUES('testPeerId','testAccountId','5rvhvriKJZ3S9oxYToVj5TzDM9u9y8cxg7htIMWlYAg=','72546A29-6BC8-4311-BCFC-9CDBF33F1A48','"100.64.114.31"','test-host-1','linux','Linux','','unknown','Debian GNU/Linux','','0.12.0','','',NULL,'','','','{"Cloud":"","Platform":""}',NULL,'test-peer-1','test-peer-1','2023-03-02 09:21:02.189035775+01:00',0,0,0,'testUserId','ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILzUUSYG/LGnV8zarb2SGN+tib/PZ+M7cL4WtTzUrTpk',0,1,'2023-03-01 19:48:19.817799698+01:00','2024-10-02 17:00:32.527947+02:00',0,'""','','',0); +INSERT INTO peers VALUES('testPeerId2','testAccountId','6rvhvriKJZ3S9oxYToVj5TzDM9u9y8cxg7htIMWlYBg=','82546A29-6BC8-4311-BCFC-9CDBF33F1A49','"100.64.114.32"','test-host-2','linux','Linux','','unknown','Ubuntu','','0.12.0','','',NULL,'','','','{"Cloud":"","Platform":""}',NULL,'test-peer-2','test-peer-2','2023-03-02 09:21:02.189035775+01:00',1,0,0,'testAdminId','ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILzUUSYG/LGnV8zarb2SGN+tib/PZ+M7cL4WtTzUrTpk',1,0,'2023-03-01 19:48:19.817799698+01:00','2024-10-02 17:00:32.527947+02:00',0,'""','','',0); \ No newline at end of file diff --git a/management/server/http/testing/testdata/policies.sql b/management/server/http/testing/testdata/policies.sql new file mode 100644 index 000000000..7e6cc883b --- /dev/null +++ b/management/server/http/testing/testdata/policies.sql @@ -0,0 +1,23 @@ +CREATE TABLE `accounts` (`id` text,`created_by` text,`created_at` datetime,`domain` text,`domain_category` text,`is_domain_primary_account` numeric,`network_identifier` text,`network_net` text,`network_dns` text,`network_serial` integer,`dns_settings_disabled_management_groups` text,`settings_peer_login_expiration_enabled` numeric,`settings_peer_login_expiration` integer,`settings_regular_users_view_blocked` numeric,`settings_groups_propagation_enabled` numeric,`settings_jwt_groups_enabled` numeric,`settings_jwt_groups_claim_name` text,`settings_jwt_allow_groups` text,`settings_extra_peer_approval_enabled` numeric,`settings_extra_integrated_validator_groups` text,PRIMARY KEY (`id`)); +CREATE TABLE `users` (`id` text,`account_id` text,`role` text,`is_service_user` numeric,`non_deletable` numeric,`service_user_name` text,`auto_groups` text,`blocked` numeric,`last_login` datetime DEFAULT NULL,`created_at` datetime,`issued` text DEFAULT "api",`integration_ref_id` integer,`integration_ref_integration_type` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_users_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `groups` (`id` text,`account_id` text,`name` text,`issued` text,`peers` text,`integration_ref_id` integer,`integration_ref_integration_type` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_groups_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `setup_keys` (`id` text,`account_id` text,`key` text,`key_secret` text,`name` text,`type` text,`created_at` datetime,`expires_at` datetime,`updated_at` datetime,`revoked` numeric,`used_times` integer,`last_used` datetime DEFAULT NULL,`auto_groups` text,`usage_limit` integer,`ephemeral` numeric,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_setup_keys_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `peers` (`id` text,`account_id` text,`key` text,`setup_key` text,`ip` text,`meta_hostname` text,`meta_go_os` text,`meta_kernel` text,`meta_core` text,`meta_platform` text,`meta_os` text,`meta_os_version` text,`meta_wt_version` text,`meta_ui_version` text,`meta_kernel_version` text,`meta_network_addresses` text,`meta_system_serial_number` text,`meta_system_product_name` text,`meta_system_manufacturer` text,`meta_environment` text,`meta_files` text,`name` text,`dns_label` text,`peer_status_last_seen` datetime,`peer_status_connected` numeric,`peer_status_login_expired` numeric,`peer_status_requires_approval` numeric,`user_id` text,`ssh_key` text,`ssh_enabled` numeric,`login_expiration_enabled` numeric,`last_login` datetime,`created_at` datetime,`ephemeral` numeric,`location_connection_ip` text,`location_country_code` text,`location_city_name` text,`location_geo_name_id` integer,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_peers_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `policies` (`id` text,`account_id` text,`name` text,`description` text,`enabled` numeric,`source_posture_checks` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_policies_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `policy_rules` (`id` text,`policy_id` text,`name` text,`description` text,`enabled` numeric,`action` text,`protocol` text,`bidirectional` numeric,`sources` text,`destinations` text,`source_resource` text,`destination_resource` text,`ports` text,`port_ranges` text,`authorized_groups` text,`authorized_user` text,PRIMARY KEY (`id`),CONSTRAINT `fk_policies_rules_g` FOREIGN KEY (`policy_id`) REFERENCES `policies`(`id`)); + +INSERT INTO accounts VALUES('testAccountId','','2024-10-02 16:01:38.000000000+00:00','test.com','private',1,'testNetworkIdentifier','{"IP":"100.64.0.0","Mask":"//8AAA=="}','',0,'[]',1,86400000000000,0,0,0,'',NULL,NULL,NULL); +INSERT INTO users VALUES('testUserId','testAccountId','user',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testAdminId','testAccountId','admin',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testOwnerId','testAccountId','owner',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testServiceUserId','testAccountId','user',1,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testServiceAdminId','testAccountId','admin',1,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('blockedUserId','testAccountId','admin',0,0,'','[]',1,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('otherUserId','otherAccountId','admin',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO "groups" VALUES('testGroupId','testAccountId','testGroupName','api','["testPeerId"]',0,''); +INSERT INTO "groups" VALUES('newGroupId','testAccountId','newGroupName','api','[]',0,''); +INSERT INTO setup_keys VALUES('testKeyId','testAccountId','testKey','testK****','existingKey','one-off','2021-08-19 20:46:20.000000000+00:00','2321-09-18 20:46:20.000000000+00:00','2021-08-19 20:46:20.000000000+00:000',0,0,NULL,'["testGroupId"]',1,0); +INSERT INTO peers VALUES('testPeerId','testAccountId','5rvhvriKJZ3S9oxYToVj5TzDM9u9y8cxg7htIMWlYAg=','72546A29-6BC8-4311-BCFC-9CDBF33F1A48','"100.64.114.31"','f2a34f6a4731','linux','Linux','11','unknown','Debian GNU/Linux','','0.12.0','','',NULL,'','','','{"Cloud":"","Platform":""}',NULL,'f2a34f6a4731','f2a34f6a4731','2023-03-02 09:21:02.189035775+01:00',0,0,0,'','ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILzUUSYG/LGnV8zarb2SGN+tib/PZ+M7cL4WtTzUrTpk',0,1,'2023-03-01 19:48:19.817799698+01:00','2024-10-02 17:00:32.527947+02:00',0,'""','','',0); + +INSERT INTO policies VALUES('testPolicyId','testAccountId','testPolicy','test policy description',1,NULL); +INSERT INTO policy_rules VALUES('testRuleId','testPolicyId','testRule','test rule',1,'accept','all',1,'["testGroupId"]','["testGroupId"]',NULL,NULL,NULL,NULL,NULL,''); \ No newline at end of file diff --git a/management/server/http/testing/testdata/routes.sql b/management/server/http/testing/testdata/routes.sql new file mode 100644 index 000000000..48aa02052 --- /dev/null +++ b/management/server/http/testing/testdata/routes.sql @@ -0,0 +1,23 @@ +CREATE TABLE `accounts` (`id` text,`created_by` text,`created_at` datetime,`domain` text,`domain_category` text,`is_domain_primary_account` numeric,`network_identifier` text,`network_net` text,`network_dns` text,`network_serial` integer,`dns_settings_disabled_management_groups` text,`settings_peer_login_expiration_enabled` numeric,`settings_peer_login_expiration` integer,`settings_regular_users_view_blocked` numeric,`settings_groups_propagation_enabled` numeric,`settings_jwt_groups_enabled` numeric,`settings_jwt_groups_claim_name` text,`settings_jwt_allow_groups` text,`settings_extra_peer_approval_enabled` numeric,`settings_extra_integrated_validator_groups` text,PRIMARY KEY (`id`)); +CREATE TABLE `users` (`id` text,`account_id` text,`role` text,`is_service_user` numeric,`non_deletable` numeric,`service_user_name` text,`auto_groups` text,`blocked` numeric,`last_login` datetime DEFAULT NULL,`created_at` datetime,`issued` text DEFAULT "api",`integration_ref_id` integer,`integration_ref_integration_type` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_users_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `groups` (`id` text,`account_id` text,`name` text,`issued` text,`peers` text,`integration_ref_id` integer,`integration_ref_integration_type` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_groups_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `setup_keys` (`id` text,`account_id` text,`key` text,`key_secret` text,`name` text,`type` text,`created_at` datetime,`expires_at` datetime,`updated_at` datetime,`revoked` numeric,`used_times` integer,`last_used` datetime DEFAULT NULL,`auto_groups` text,`usage_limit` integer,`ephemeral` numeric,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_setup_keys_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `peers` (`id` text,`account_id` text,`key` text,`setup_key` text,`ip` text,`meta_hostname` text,`meta_go_os` text,`meta_kernel` text,`meta_core` text,`meta_platform` text,`meta_os` text,`meta_os_version` text,`meta_wt_version` text,`meta_ui_version` text,`meta_kernel_version` text,`meta_network_addresses` text,`meta_system_serial_number` text,`meta_system_product_name` text,`meta_system_manufacturer` text,`meta_environment` text,`meta_files` text,`name` text,`dns_label` text,`peer_status_last_seen` datetime,`peer_status_connected` numeric,`peer_status_login_expired` numeric,`peer_status_requires_approval` numeric,`user_id` text,`ssh_key` text,`ssh_enabled` numeric,`login_expiration_enabled` numeric,`last_login` datetime,`created_at` datetime,`ephemeral` numeric,`location_connection_ip` text,`location_country_code` text,`location_city_name` text,`location_geo_name_id` integer,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_peers_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `routes` (`id` text,`account_id` text,`network` text,`domains` text,`keep_route` numeric,`net_id` text,`description` text,`peer` text,`peer_groups` text,`network_type` integer,`masquerade` numeric,`metric` integer,`enabled` numeric,`groups` text,`access_control_groups` text,`skip_auto_apply` numeric,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_routes_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); + +INSERT INTO accounts VALUES('testAccountId','','2024-10-02 16:01:38.000000000+00:00','test.com','private',1,'testNetworkIdentifier','{"IP":"100.64.0.0","Mask":"//8AAA=="}','',0,'[]',1,86400000000000,0,0,0,'',NULL,NULL,NULL); +INSERT INTO users VALUES('testUserId','testAccountId','user',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testAdminId','testAccountId','admin',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testOwnerId','testAccountId','owner',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testServiceUserId','testAccountId','user',1,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testServiceAdminId','testAccountId','admin',1,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('blockedUserId','testAccountId','admin',0,0,'','[]',1,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('otherUserId','otherAccountId','admin',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO "groups" VALUES('testGroupId','testAccountId','testGroupName','api','["testPeerId"]',0,''); +INSERT INTO "groups" VALUES('newGroupId','testAccountId','newGroupName','api','[]',0,''); +INSERT INTO "groups" VALUES('peerGroupId','testAccountId','peerGroupName','api','["testPeerId"]',0,''); +INSERT INTO setup_keys VALUES('testKeyId','testAccountId','testKey','testK****','existingKey','one-off','2021-08-19 20:46:20.000000000+00:00','2321-09-18 20:46:20.000000000+00:00','2021-08-19 20:46:20.000000000+00:000',0,0,NULL,'["testGroupId"]',1,0); +INSERT INTO peers VALUES('testPeerId','testAccountId','5rvhvriKJZ3S9oxYToVj5TzDM9u9y8cxg7htIMWlYAg=','72546A29-6BC8-4311-BCFC-9CDBF33F1A48','"100.64.114.31"','f2a34f6a4731','linux','Linux','11','unknown','Debian GNU/Linux','','0.12.0','','',NULL,'','','','{"Cloud":"","Platform":""}',NULL,'f2a34f6a4731','f2a34f6a4731','2023-03-02 09:21:02.189035775+01:00',0,0,0,'','ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILzUUSYG/LGnV8zarb2SGN+tib/PZ+M7cL4WtTzUrTpk',0,1,'2023-03-01 19:48:19.817799698+01:00','2024-10-02 17:00:32.527947+02:00',0,'""','','',0); + +INSERT INTO routes VALUES('testRouteId','testAccountId','"10.0.0.0/24"',NULL,0,'testNet','Test Network Route','testPeerId',NULL,1,1,100,1,'["testGroupId"]',NULL,0); +INSERT INTO routes VALUES('testDomainRouteId','testAccountId','"0.0.0.0/0"','["example.com"]',0,'testDomainNet','Test Domain Route','','["peerGroupId"]',3,1,200,1,'["testGroupId"]',NULL,0); diff --git a/management/server/http/testing/testdata/users_integration.sql b/management/server/http/testing/testdata/users_integration.sql new file mode 100644 index 000000000..57df73e8c --- /dev/null +++ b/management/server/http/testing/testdata/users_integration.sql @@ -0,0 +1,24 @@ +CREATE TABLE `accounts` (`id` text,`created_by` text,`created_at` datetime,`domain` text,`domain_category` text,`is_domain_primary_account` numeric,`network_identifier` text,`network_net` text,`network_dns` text,`network_serial` integer,`dns_settings_disabled_management_groups` text,`settings_peer_login_expiration_enabled` numeric,`settings_peer_login_expiration` integer,`settings_regular_users_view_blocked` numeric,`settings_groups_propagation_enabled` numeric,`settings_jwt_groups_enabled` numeric,`settings_jwt_groups_claim_name` text,`settings_jwt_allow_groups` text,`settings_extra_peer_approval_enabled` numeric,`settings_extra_integrated_validator_groups` text,PRIMARY KEY (`id`)); +CREATE TABLE `users` (`id` text,`account_id` text,`role` text,`is_service_user` numeric,`non_deletable` numeric,`service_user_name` text,`auto_groups` text,`blocked` numeric,`last_login` datetime DEFAULT NULL,`created_at` datetime,`issued` text DEFAULT "api",`integration_ref_id` integer,`integration_ref_integration_type` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_users_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `groups` (`id` text,`account_id` text,`name` text,`issued` text,`peers` text,`integration_ref_id` integer,`integration_ref_integration_type` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_groups_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `setup_keys` (`id` text,`account_id` text,`key` text,`key_secret` text,`name` text,`type` text,`created_at` datetime,`expires_at` datetime,`updated_at` datetime,`revoked` numeric,`used_times` integer,`last_used` datetime DEFAULT NULL,`auto_groups` text,`usage_limit` integer,`ephemeral` numeric,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_setup_keys_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `peers` (`id` text,`account_id` text,`key` text,`setup_key` text,`ip` text,`meta_hostname` text,`meta_go_os` text,`meta_kernel` text,`meta_core` text,`meta_platform` text,`meta_os` text,`meta_os_version` text,`meta_wt_version` text,`meta_ui_version` text,`meta_kernel_version` text,`meta_network_addresses` text,`meta_system_serial_number` text,`meta_system_product_name` text,`meta_system_manufacturer` text,`meta_environment` text,`meta_files` text,`name` text,`dns_label` text,`peer_status_last_seen` datetime,`peer_status_connected` numeric,`peer_status_login_expired` numeric,`peer_status_requires_approval` numeric,`user_id` text,`ssh_key` text,`ssh_enabled` numeric,`login_expiration_enabled` numeric,`last_login` datetime,`created_at` datetime,`ephemeral` numeric,`location_connection_ip` text,`location_country_code` text,`location_city_name` text,`location_geo_name_id` integer,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_peers_g` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); +CREATE TABLE `personal_access_tokens` (`id` text,`user_id` text,`name` text,`hashed_token` text,`expiration_date` datetime,`created_by` text,`created_at` datetime,`last_used` datetime DEFAULT NULL,PRIMARY KEY (`id`),CONSTRAINT `fk_users_pa_ts_g` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`)); +CREATE INDEX `idx_personal_access_tokens_user_id` ON `personal_access_tokens`(`user_id`); + +INSERT INTO accounts VALUES('testAccountId','','2024-10-02 16:01:38.000000000+00:00','test.com','private',1,'testNetworkIdentifier','{"IP":"100.64.0.0","Mask":"//8AAA=="}','',0,'[]',1,86400000000000,0,0,0,'',NULL,NULL,NULL); +INSERT INTO users VALUES('testUserId','testAccountId','user',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testAdminId','testAccountId','admin',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testOwnerId','testAccountId','owner',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testServiceUserId','testAccountId','user',1,0,'testServiceUser','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('testServiceAdminId','testAccountId','admin',1,0,'testServiceAdmin','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('blockedUserId','testAccountId','admin',0,0,'','[]',1,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('otherUserId','otherAccountId','admin',0,0,'','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO users VALUES('deletableServiceUserId','testAccountId','user',1,0,'deletableServiceUser','[]',0,NULL,'2024-10-02 16:01:38.000000000+00:00','api',0,''); +INSERT INTO "groups" VALUES('testGroupId','testAccountId','testGroupName','api','["testPeerId"]',0,''); +INSERT INTO "groups" VALUES('newGroupId','testAccountId','newGroupName','api','[]',0,''); +INSERT INTO setup_keys VALUES('testKeyId','testAccountId','testKey','testK****','existingKey','one-off','2021-08-19 20:46:20.000000000+00:00','2321-09-18 20:46:20.000000000+00:00','2021-08-19 20:46:20.000000000+00:000',0,0,NULL,'["testGroupId"]',1,0); +INSERT INTO peers VALUES('testPeerId','testAccountId','5rvhvriKJZ3S9oxYToVj5TzDM9u9y8cxg7htIMWlYAg=','72546A29-6BC8-4311-BCFC-9CDBF33F1A48','"100.64.114.31"','f2a34f6a4731','linux','Linux','11','unknown','Debian GNU/Linux','','0.12.0','','',NULL,'','','','{"Cloud":"","Platform":""}',NULL,'f2a34f6a4731','f2a34f6a4731','2023-03-02 09:21:02.189035775+01:00',0,0,0,'','ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILzUUSYG/LGnV8zarb2SGN+tib/PZ+M7cL4WtTzUrTpk',0,1,'2023-03-01 19:48:19.817799698+01:00','2024-10-02 17:00:32.527947+02:00',0,'""','','',0); + +INSERT INTO personal_access_tokens VALUES('testTokenId','testUserId','testToken','hashedTokenValue123','2325-10-02 16:01:38.000000000+00:00','testUserId','2024-10-02 16:01:38.000000000+00:00',NULL); +INSERT INTO personal_access_tokens VALUES('serviceTokenId','testServiceUserId','serviceToken','hashedServiceTokenValue123','2325-10-02 16:01:38.000000000+00:00','testAdminId','2024-10-02 16:01:38.000000000+00:00',NULL); \ No newline at end of file diff --git a/management/server/http/testing/testing_tools/channel/channel.go b/management/server/http/testing/testing_tools/channel/channel.go index 6bd269a2c..55095bbb7 100644 --- a/management/server/http/testing/testing_tools/channel/channel.go +++ b/management/server/http/testing/testing_tools/channel/channel.go @@ -128,14 +128,14 @@ func BuildApiBlackBoxWithDBState(t testing_tools.TB, sqlFile string, expectedPee GetPATInfoFunc: authManager.GetPATInfo, } - networksManagerMock := networks.NewManagerMock() - resourcesManagerMock := resources.NewManagerMock() - routersManagerMock := routers.NewManagerMock() - groupsManagerMock := groups.NewManagerMock() + groupsManager := groups.NewManager(store, permissionsManager, am) + routersManager := routers.NewManager(store, permissionsManager, am) + resourcesManager := resources.NewManager(store, permissionsManager, groupsManager, am, serviceManager) + networksManager := networks.NewManager(store, permissionsManager, resourcesManager, routersManager, am) customZonesManager := zonesManager.NewManager(store, am, permissionsManager, "") zoneRecordsManager := recordsManager.NewManager(store, am, permissionsManager) - apiHandler, err := http2.NewAPIHandler(context.Background(), am, networksManagerMock, resourcesManagerMock, routersManagerMock, groupsManagerMock, geoMock, authManagerMock, metrics, validatorMock, proxyController, permissionsManager, peersManager, settingsManager, customZonesManager, zoneRecordsManager, networkMapController, nil, serviceManager, nil, nil, nil, nil) + apiHandler, err := http2.NewAPIHandler(context.Background(), am, networksManager, resourcesManager, routersManager, groupsManager, geoMock, authManagerMock, metrics, validatorMock, proxyController, permissionsManager, peersManager, settingsManager, customZonesManager, zoneRecordsManager, networkMapController, nil, serviceManager, nil, nil, nil, nil) if err != nil { t.Fatalf("Failed to create API handler: %v", err) } @@ -167,6 +167,112 @@ func peerShouldReceiveUpdate(t testing_tools.TB, updateMessage <-chan *network_m } } +// PeerShouldReceiveAnyUpdate waits for a peer update message and returns it. +// Fails the test if no update is received within timeout. +func PeerShouldReceiveAnyUpdate(t testing_tools.TB, updateMessage <-chan *network_map.UpdateMessage) *network_map.UpdateMessage { + t.Helper() + select { + case msg := <-updateMessage: + if msg == nil { + t.Errorf("Received nil update message, expected valid message") + } + return msg + case <-time.After(500 * time.Millisecond): + t.Errorf("Timed out waiting for update message") + return nil + } +} + +// PeerShouldNotReceiveAnyUpdate verifies no peer update message is received. +func PeerShouldNotReceiveAnyUpdate(t testing_tools.TB, updateMessage <-chan *network_map.UpdateMessage) { + t.Helper() + peerShouldNotReceiveUpdate(t, updateMessage) +} + +// BuildApiBlackBoxWithDBStateAndPeerChannel creates the API handler and returns +// the peer update channel directly so tests can verify updates inline. +func BuildApiBlackBoxWithDBStateAndPeerChannel(t testing_tools.TB, sqlFile string) (http.Handler, account.Manager, <-chan *network_map.UpdateMessage) { + store, cleanup, err := store.NewTestStoreFromSQL(context.Background(), sqlFile, t.TempDir()) + if err != nil { + t.Fatalf("Failed to create test store: %v", err) + } + t.Cleanup(cleanup) + + metrics, err := telemetry.NewDefaultAppMetrics(context.Background()) + if err != nil { + t.Fatalf("Failed to create metrics: %v", err) + } + + peersUpdateManager := update_channel.NewPeersUpdateManager(nil) + updMsg := peersUpdateManager.CreateChannel(context.Background(), testing_tools.TestPeerId) + + geoMock := &geolocation.Mock{} + validatorMock := server.MockIntegratedValidator{} + proxyController := integrations.NewController(store) + userManager := users.NewManager(store) + permissionsManager := permissions.NewManager(store) + settingsManager := settings.NewManager(store, userManager, integrations.NewManager(&activity.InMemoryEventStore{}), permissionsManager, settings.IdpConfig{}) + peersManager := peers.NewManager(store, permissionsManager) + + jobManager := job.NewJobManager(nil, store, peersManager) + + ctx := context.Background() + requestBuffer := server.NewAccountRequestBuffer(ctx, store) + networkMapController := controller.NewController(ctx, store, metrics, peersUpdateManager, requestBuffer, server.MockIntegratedValidator{}, settingsManager, "", port_forwarding.NewControllerMock(), ephemeral_manager.NewEphemeralManager(store, peersManager), &config.Config{}) + am, err := server.BuildManager(ctx, nil, store, networkMapController, jobManager, nil, "", &activity.InMemoryEventStore{}, geoMock, false, validatorMock, metrics, proxyController, settingsManager, permissionsManager, false) + if err != nil { + t.Fatalf("Failed to create manager: %v", err) + } + + accessLogsManager := accesslogsmanager.NewManager(store, permissionsManager, nil) + proxyTokenStore, err := nbgrpc.NewOneTimeTokenStore(ctx, 5*time.Minute, 10*time.Minute, 100) + if err != nil { + t.Fatalf("Failed to create proxy token store: %v", err) + } + pkceverifierStore, err := nbgrpc.NewPKCEVerifierStore(ctx, 10*time.Minute, 10*time.Minute, 100) + if err != nil { + t.Fatalf("Failed to create PKCE verifier store: %v", err) + } + noopMeter := noop.NewMeterProvider().Meter("") + proxyMgr, err := proxymanager.NewManager(store, noopMeter) + if err != nil { + t.Fatalf("Failed to create proxy manager: %v", err) + } + proxyServiceServer := nbgrpc.NewProxyServiceServer(accessLogsManager, proxyTokenStore, pkceverifierStore, nbgrpc.ProxyOIDCConfig{}, peersManager, userManager, proxyMgr) + domainManager := manager.NewManager(store, proxyMgr, permissionsManager, am) + serviceProxyController, err := proxymanager.NewGRPCController(proxyServiceServer, noopMeter) + if err != nil { + t.Fatalf("Failed to create proxy controller: %v", err) + } + domainManager.SetClusterCapabilities(serviceProxyController) + serviceManager := reverseproxymanager.NewManager(store, am, permissionsManager, serviceProxyController, domainManager) + proxyServiceServer.SetServiceManager(serviceManager) + am.SetServiceManager(serviceManager) + + // @note this is required so that PAT's validate from store, but JWT's are mocked + authManager := serverauth.NewManager(store, "", "", "", "", []string{}, false) + authManagerMock := &serverauth.MockManager{ + ValidateAndParseTokenFunc: mockValidateAndParseToken, + EnsureUserAccessByJWTGroupsFunc: authManager.EnsureUserAccessByJWTGroups, + MarkPATUsedFunc: authManager.MarkPATUsed, + GetPATInfoFunc: authManager.GetPATInfo, + } + + groupsManager := groups.NewManager(store, permissionsManager, am) + routersManager := routers.NewManager(store, permissionsManager, am) + resourcesManager := resources.NewManager(store, permissionsManager, groupsManager, am, serviceManager) + networksManager := networks.NewManager(store, permissionsManager, resourcesManager, routersManager, am) + customZonesManager := zonesManager.NewManager(store, am, permissionsManager, "") + zoneRecordsManager := recordsManager.NewManager(store, am, permissionsManager) + + apiHandler, err := http2.NewAPIHandler(context.Background(), am, networksManager, resourcesManager, routersManager, groupsManager, geoMock, authManagerMock, metrics, validatorMock, proxyController, permissionsManager, peersManager, settingsManager, customZonesManager, zoneRecordsManager, networkMapController, nil, serviceManager, nil, nil, nil, nil) + if err != nil { + t.Fatalf("Failed to create API handler: %v", err) + } + + return apiHandler, am, updMsg +} + func mockValidateAndParseToken(_ context.Context, token string) (auth.UserAuth, *jwt.Token, error) { userAuth := auth.UserAuth{} diff --git a/management/server/http/testing/testing_tools/db_verify.go b/management/server/http/testing/testing_tools/db_verify.go new file mode 100644 index 000000000..f8af6a41f --- /dev/null +++ b/management/server/http/testing/testing_tools/db_verify.go @@ -0,0 +1,222 @@ +package testing_tools + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gorm.io/gorm" + + nbdns "github.com/netbirdio/netbird/dns" + resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types" + routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types" + networkTypes "github.com/netbirdio/netbird/management/server/networks/types" + nbpeer "github.com/netbirdio/netbird/management/server/peer" + "github.com/netbirdio/netbird/management/server/store" + "github.com/netbirdio/netbird/management/server/types" + "github.com/netbirdio/netbird/route" +) + +// GetDB extracts the *gorm.DB from a store.Store (must be *SqlStore). +func GetDB(t *testing.T, s store.Store) *gorm.DB { + t.Helper() + sqlStore, ok := s.(*store.SqlStore) + require.True(t, ok, "Store is not a *SqlStore, cannot get gorm.DB") + return sqlStore.GetDB() +} + +// VerifyGroupInDB reads a group directly from the DB and returns it. +func VerifyGroupInDB(t *testing.T, db *gorm.DB, groupID string) *types.Group { + t.Helper() + var group types.Group + err := db.Where("id = ? AND account_id = ?", groupID, TestAccountId).First(&group).Error + require.NoError(t, err, "Expected group %s to exist in DB", groupID) + return &group +} + +// VerifyGroupNotInDB verifies that a group does not exist in the DB. +func VerifyGroupNotInDB(t *testing.T, db *gorm.DB, groupID string) { + t.Helper() + var count int64 + db.Model(&types.Group{}).Where("id = ? AND account_id = ?", groupID, TestAccountId).Count(&count) + assert.Equal(t, int64(0), count, "Expected group %s to NOT exist in DB", groupID) +} + +// VerifyPolicyInDB reads a policy directly from the DB and returns it. +func VerifyPolicyInDB(t *testing.T, db *gorm.DB, policyID string) *types.Policy { + t.Helper() + var policy types.Policy + err := db.Preload("Rules").Where("id = ? AND account_id = ?", policyID, TestAccountId).First(&policy).Error + require.NoError(t, err, "Expected policy %s to exist in DB", policyID) + return &policy +} + +// VerifyPolicyNotInDB verifies that a policy does not exist in the DB. +func VerifyPolicyNotInDB(t *testing.T, db *gorm.DB, policyID string) { + t.Helper() + var count int64 + db.Model(&types.Policy{}).Where("id = ? AND account_id = ?", policyID, TestAccountId).Count(&count) + assert.Equal(t, int64(0), count, "Expected policy %s to NOT exist in DB", policyID) +} + +// VerifyRouteInDB reads a route directly from the DB and returns it. +func VerifyRouteInDB(t *testing.T, db *gorm.DB, routeID route.ID) *route.Route { + t.Helper() + var r route.Route + err := db.Where("id = ? AND account_id = ?", routeID, TestAccountId).First(&r).Error + require.NoError(t, err, "Expected route %s to exist in DB", routeID) + return &r +} + +// VerifyRouteNotInDB verifies that a route does not exist in the DB. +func VerifyRouteNotInDB(t *testing.T, db *gorm.DB, routeID route.ID) { + t.Helper() + var count int64 + db.Model(&route.Route{}).Where("id = ? AND account_id = ?", routeID, TestAccountId).Count(&count) + assert.Equal(t, int64(0), count, "Expected route %s to NOT exist in DB", routeID) +} + +// VerifyNSGroupInDB reads a nameserver group directly from the DB and returns it. +func VerifyNSGroupInDB(t *testing.T, db *gorm.DB, nsGroupID string) *nbdns.NameServerGroup { + t.Helper() + var nsGroup nbdns.NameServerGroup + err := db.Where("id = ? AND account_id = ?", nsGroupID, TestAccountId).First(&nsGroup).Error + require.NoError(t, err, "Expected NS group %s to exist in DB", nsGroupID) + return &nsGroup +} + +// VerifyNSGroupNotInDB verifies that a nameserver group does not exist in the DB. +func VerifyNSGroupNotInDB(t *testing.T, db *gorm.DB, nsGroupID string) { + t.Helper() + var count int64 + db.Model(&nbdns.NameServerGroup{}).Where("id = ? AND account_id = ?", nsGroupID, TestAccountId).Count(&count) + assert.Equal(t, int64(0), count, "Expected NS group %s to NOT exist in DB", nsGroupID) +} + +// VerifyPeerInDB reads a peer directly from the DB and returns it. +func VerifyPeerInDB(t *testing.T, db *gorm.DB, peerID string) *nbpeer.Peer { + t.Helper() + var peer nbpeer.Peer + err := db.Where("id = ? AND account_id = ?", peerID, TestAccountId).First(&peer).Error + require.NoError(t, err, "Expected peer %s to exist in DB", peerID) + return &peer +} + +// VerifyPeerNotInDB verifies that a peer does not exist in the DB. +func VerifyPeerNotInDB(t *testing.T, db *gorm.DB, peerID string) { + t.Helper() + var count int64 + db.Model(&nbpeer.Peer{}).Where("id = ? AND account_id = ?", peerID, TestAccountId).Count(&count) + assert.Equal(t, int64(0), count, "Expected peer %s to NOT exist in DB", peerID) +} + +// VerifySetupKeyInDB reads a setup key directly from the DB and returns it. +func VerifySetupKeyInDB(t *testing.T, db *gorm.DB, keyID string) *types.SetupKey { + t.Helper() + var key types.SetupKey + err := db.Where("id = ? AND account_id = ?", keyID, TestAccountId).First(&key).Error + require.NoError(t, err, "Expected setup key %s to exist in DB", keyID) + return &key +} + +// VerifySetupKeyNotInDB verifies that a setup key does not exist in the DB. +func VerifySetupKeyNotInDB(t *testing.T, db *gorm.DB, keyID string) { + t.Helper() + var count int64 + db.Model(&types.SetupKey{}).Where("id = ? AND account_id = ?", keyID, TestAccountId).Count(&count) + assert.Equal(t, int64(0), count, "Expected setup key %s to NOT exist in DB", keyID) +} + +// VerifyUserInDB reads a user directly from the DB and returns it. +func VerifyUserInDB(t *testing.T, db *gorm.DB, userID string) *types.User { + t.Helper() + var user types.User + err := db.Where("id = ? AND account_id = ?", userID, TestAccountId).First(&user).Error + require.NoError(t, err, "Expected user %s to exist in DB", userID) + return &user +} + +// VerifyUserNotInDB verifies that a user does not exist in the DB. +func VerifyUserNotInDB(t *testing.T, db *gorm.DB, userID string) { + t.Helper() + var count int64 + db.Model(&types.User{}).Where("id = ? AND account_id = ?", userID, TestAccountId).Count(&count) + assert.Equal(t, int64(0), count, "Expected user %s to NOT exist in DB", userID) +} + +// VerifyPATInDB reads a PAT directly from the DB and returns it. +func VerifyPATInDB(t *testing.T, db *gorm.DB, tokenID string) *types.PersonalAccessToken { + t.Helper() + var pat types.PersonalAccessToken + err := db.Where("id = ?", tokenID).First(&pat).Error + require.NoError(t, err, "Expected PAT %s to exist in DB", tokenID) + return &pat +} + +// VerifyPATNotInDB verifies that a PAT does not exist in the DB. +func VerifyPATNotInDB(t *testing.T, db *gorm.DB, tokenID string) { + t.Helper() + var count int64 + db.Model(&types.PersonalAccessToken{}).Where("id = ?", tokenID).Count(&count) + assert.Equal(t, int64(0), count, "Expected PAT %s to NOT exist in DB", tokenID) +} + +// VerifyAccountSettings reads the account and returns its settings from the DB. +func VerifyAccountSettings(t *testing.T, db *gorm.DB) *types.Account { + t.Helper() + var account types.Account + err := db.Where("id = ?", TestAccountId).First(&account).Error + require.NoError(t, err, "Expected account %s to exist in DB", TestAccountId) + return &account +} + +// VerifyNetworkInDB reads a network directly from the store and returns it. +func VerifyNetworkInDB(t *testing.T, db *gorm.DB, networkID string) *networkTypes.Network { + t.Helper() + var network networkTypes.Network + err := db.Where("id = ? AND account_id = ?", networkID, TestAccountId).First(&network).Error + require.NoError(t, err, "Expected network %s to exist in DB", networkID) + return &network +} + +// VerifyNetworkNotInDB verifies that a network does not exist in the DB. +func VerifyNetworkNotInDB(t *testing.T, db *gorm.DB, networkID string) { + t.Helper() + var count int64 + db.Model(&networkTypes.Network{}).Where("id = ? AND account_id = ?", networkID, TestAccountId).Count(&count) + assert.Equal(t, int64(0), count, "Expected network %s to NOT exist in DB", networkID) +} + +// VerifyNetworkResourceInDB reads a network resource directly from the DB and returns it. +func VerifyNetworkResourceInDB(t *testing.T, db *gorm.DB, resourceID string) *resourceTypes.NetworkResource { + t.Helper() + var resource resourceTypes.NetworkResource + err := db.Where("id = ? AND account_id = ?", resourceID, TestAccountId).First(&resource).Error + require.NoError(t, err, "Expected network resource %s to exist in DB", resourceID) + return &resource +} + +// VerifyNetworkResourceNotInDB verifies that a network resource does not exist in the DB. +func VerifyNetworkResourceNotInDB(t *testing.T, db *gorm.DB, resourceID string) { + t.Helper() + var count int64 + db.Model(&resourceTypes.NetworkResource{}).Where("id = ? AND account_id = ?", resourceID, TestAccountId).Count(&count) + assert.Equal(t, int64(0), count, "Expected network resource %s to NOT exist in DB", resourceID) +} + +// VerifyNetworkRouterInDB reads a network router directly from the DB and returns it. +func VerifyNetworkRouterInDB(t *testing.T, db *gorm.DB, routerID string) *routerTypes.NetworkRouter { + t.Helper() + var router routerTypes.NetworkRouter + err := db.Where("id = ? AND account_id = ?", routerID, TestAccountId).First(&router).Error + require.NoError(t, err, "Expected network router %s to exist in DB", routerID) + return &router +} + +// VerifyNetworkRouterNotInDB verifies that a network router does not exist in the DB. +func VerifyNetworkRouterNotInDB(t *testing.T, db *gorm.DB, routerID string) { + t.Helper() + var count int64 + db.Model(&routerTypes.NetworkRouter{}).Where("id = ? AND account_id = ?", routerID, TestAccountId).Count(&count) + assert.Equal(t, int64(0), count, "Expected network router %s to NOT exist in DB", routerID) +}