package client import ( "fmt" "net/http" "net/http/httptest" "sort" "sync" "testing" "github.com/sparetimecoders/goamqp" "github.com/stretchr/testify/assert" ) func TestPrivilegeHandler_Process_InvalidType(t *testing.T) { handler := New(WithBaseURL("base")) result, err := handler.Process("abc", goamqp.Headers{}) assert.Nil(t, result) assert.EqualError(t, err, "unexpected event type: 'string'") } func TestPrivilegeHandler_Process_PrivilegeRemoved(t *testing.T) { handler := New(WithBaseURL("base")) result, err := handler.Process(&PrivilegeAdded{ Email: "jim@example.org", CompanyID: "abc-123", Privilege: PrivilegeAdmin, }, goamqp.Headers{}) assert.Nil(t, result) assert.NoError(t, err) companies := handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool { return privileges.Admin }) assert.Equal(t, []string{"abc-123"}, companies) result, err = handler.Process(&PrivilegeRemoved{ Email: "jim@example.org", CompanyID: "abc-123", Privilege: PrivilegeAdmin, }, goamqp.Headers{}) assert.Nil(t, result) assert.NoError(t, err) companies = handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool { return privileges.Admin }) assert.Empty(t, companies) } func TestPrivilegeHandler_Process_UserAdded_And_UserRemoved(t *testing.T) { handler := New(WithBaseURL("base")) result, err := handler.Process(&UserAdded{ Email: "jim@example.org", CompanyID: "abc-123", }, goamqp.Headers{}) assert.Nil(t, result) assert.NoError(t, err) result, err = handler.Process(&UserAdded{ Email: "jim@example.org", CompanyID: "abc-456", }, goamqp.Headers{}) assert.Nil(t, result) assert.NoError(t, err) companies := handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool { return true }) sort.Strings(companies) assert.Equal(t, []string{"abc-123", "abc-456"}, companies) result, err = handler.Process(&UserRemoved{ Email: "jim@example.org", CompanyID: "abc-123", }, goamqp.Headers{}) assert.Nil(t, result) assert.NoError(t, err) result, err = handler.Process(&UserRemoved{ Email: "jim@example.org", CompanyID: "abc-456", }, goamqp.Headers{}) assert.Nil(t, result) assert.NoError(t, err) companies = handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool { return true }) assert.Empty(t, companies) } func TestPrivilegeHandler_GetCompanies_Email_Not_Found(t *testing.T) { handler := New(WithBaseURL("base")) companies := handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool { return true }) assert.Empty(t, companies) } func TestPrivilegeHandler_GetCompanies_No_Companies_Found(t *testing.T) { handler := New(WithBaseURL("base")) result, err := handler.Process(&UserAdded{ Email: "jim@example.org", CompanyID: "abc-123", }, goamqp.Headers{}) assert.Nil(t, result) assert.NoError(t, err) companies := handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool { return privileges.Admin }) assert.Empty(t, companies) companies = handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool { return true }) assert.Equal(t, []string{"abc-123"}, companies) result, err = handler.Process(&UserRemoved{ Email: "jim@example.org", CompanyID: "abc-123", }, goamqp.Headers{}) assert.Nil(t, result) assert.NoError(t, err) companies = handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool { return true }) assert.Empty(t, companies) } func TestPrivilegeHandler_GetCompanies_Company_With_Company_Access_Found(t *testing.T) { handler := New(WithBaseURL("base")) result, err := handler.Process(&PrivilegeAdded{ Email: "jim@example.org", CompanyID: "abc-123", Privilege: PrivilegeCompany, }, goamqp.Headers{}) assert.Nil(t, result) assert.NoError(t, err) companies := handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool { return privileges.Company }) assert.Equal(t, []string{"abc-123"}, companies) } func TestPrivilegeHandler_GetCompanies_Company_With_Admin_Access_Found(t *testing.T) { handler := New(WithBaseURL("base")) result, err := handler.Process(&PrivilegeAdded{ Email: "jim@example.org", CompanyID: "abc-123", Privilege: PrivilegeConsumer, }, goamqp.Headers{}) assert.Nil(t, result) assert.NoError(t, err) companies := handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool { return privileges.Consumer }) assert.Equal(t, []string{"abc-123"}, companies) } func TestPrivilegeHandler_IsAllowed_Return_False_If_No_Privileges(t *testing.T) { handler := New(WithBaseURL("base")) result := handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool { return privileges.Company }) assert.False(t, result) } func TestPrivilegeHandler_IsAllowed_Return_True_If_Privilege_Exists(t *testing.T) { handler := New(WithBaseURL("base")) _, _ = handler.Process(&PrivilegeAdded{ Email: "jim@example.org", CompanyID: "abc-123", Privilege: PrivilegeTime, }, goamqp.Headers{}) result := handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool { return privileges.Time }) assert.True(t, result) _, _ = handler.Process(&PrivilegeAdded{ Email: "jim@example.org", CompanyID: "abc-123", Privilege: PrivilegeInvoicing, }, goamqp.Headers{}) result = handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool { return privileges.Invoicing }) assert.True(t, result) _, _ = handler.Process(&PrivilegeAdded{ Email: "jim@example.org", CompanyID: "abc-123", Privilege: PrivilegeAccounting, }, goamqp.Headers{}) result = handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool { return privileges.Accounting }) assert.True(t, result) _, _ = handler.Process(&PrivilegeAdded{ Email: "jim@example.org", CompanyID: "abc-123", Privilege: PrivilegeSupplier, }, goamqp.Headers{}) result = handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool { return privileges.Supplier }) assert.True(t, result) _, _ = handler.Process(&PrivilegeAdded{ Email: "jim@example.org", CompanyID: "abc-123", Privilege: PrivilegeSalary, }, goamqp.Headers{}) result = handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool { return privileges.Salary }) assert.True(t, result) } func TestPrivilegeHandler_Fetch_Error_Response(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(500) })) baseURL := server.Listener.Addr().String() handler := New(WithBaseURL(fmt.Sprintf("http://%s", baseURL))) server.Close() err := handler.Fetch() assert.EqualError(t, err, fmt.Sprintf("Get \"http://%s/authz\": dial tcp %s: connect: connection refused", baseURL, baseURL)) } func TestPrivilegeHandler_Fetch_Error_Unreadable_Body(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Length", "1") })) defer server.Close() baseURL := server.Listener.Addr().String() handler := New(WithBaseURL(fmt.Sprintf("http://%s", baseURL))) err := handler.Fetch() assert.EqualError(t, err, "unexpected EOF") } func TestPrivilegeHandler_Fetch_Error_Broken_JSON(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte("{abc")) })) defer server.Close() baseURL := server.Listener.Addr().String() handler := New(WithBaseURL(fmt.Sprintf("http://%s", baseURL))) err := handler.Fetch() assert.EqualError(t, err, "invalid character 'a' looking for beginning of object key string") } func TestPrivilegeHandler_Fetch_Valid(t *testing.T) { privileges := ` { "jim@example.org": { "00010203-0405-4607-8809-0a0b0c0d0e0f": { "admin": false, "company": true, "consumer": false, "time": true, "invoicing": true, "accounting": false, "supplier": false, "salary": true } } }` server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(privileges)) })) defer server.Close() baseURL := server.Listener.Addr().String() handler := New(WithBaseURL(fmt.Sprintf("http://%s", baseURL))) err := handler.Fetch() assert.NoError(t, err) expectedPrivileges := map[string]map[string]*CompanyPrivileges{ "jim@example.org": { "00010203-0405-4607-8809-0a0b0c0d0e0f": { Admin: false, Company: true, Consumer: false, Time: true, Invoicing: true, Accounting: false, Supplier: false, Salary: true, }, }, } assert.Equal(t, expectedPrivileges, handler.privileges) } func TestPrivilegeHandler_Fetch_Concurrent_Fetches(t *testing.T) { privileges := ` { "jim@example.org": { "00010203-0405-4607-8809-0a0b0c0d0e0f": { "admin": false, "company": true, "consumer": false, "time": true, "invoicing": true, "accounting": false, "supplier": false, "salary": true } } }` server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(privileges)) })) defer server.Close() baseURL := server.Listener.Addr().String() handler := New(WithBaseURL(fmt.Sprintf("http://%s", baseURL))) // Run multiple Fetch calls concurrently to test thread-safety var wg sync.WaitGroup errors := make(chan error, 10) for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() if err := handler.Fetch(); err != nil { errors <- err } }() } wg.Wait() close(errors) // Check no errors occurred for err := range errors { assert.NoError(t, err) } // Verify privileges were set correctly expectedPrivileges := map[string]map[string]*CompanyPrivileges{ "jim@example.org": { "00010203-0405-4607-8809-0a0b0c0d0e0f": { Admin: false, Company: true, Consumer: false, Time: true, Invoicing: true, Accounting: false, Supplier: false, Salary: true, }, }, } assert.Equal(t, expectedPrivileges, handler.privileges) } func TestPrivilegeHandler_Concurrent_Fetch_And_Read(t *testing.T) { privileges := ` { "jim@example.org": { "abc-123": { "admin": true, "company": true, "consumer": false, "time": false, "invoicing": false, "accounting": false, "supplier": false, "salary": false } } }` server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(privileges)) })) defer server.Close() baseURL := server.Listener.Addr().String() handler := New(WithBaseURL(fmt.Sprintf("http://%s", baseURL))) var wg sync.WaitGroup errors := make(chan error, 100) // Start multiple Fetch operations for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() if err := handler.Fetch(); err != nil { errors <- err } }() } // Concurrently read privileges while Fetch is running for i := 0; i < 50; i++ { wg.Add(1) go func() { defer wg.Done() _ = handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool { return privileges.Admin }) }() } // Concurrently check privileges while Fetch is running for i := 0; i < 50; i++ { wg.Add(1) go func() { defer wg.Done() _ = handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool { return privileges.Admin }) }() } wg.Wait() close(errors) // Check no errors occurred for err := range errors { assert.NoError(t, err) } // Verify privileges are correct after all concurrent operations companies := handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool { return privileges.Admin }) assert.Equal(t, []string{"abc-123"}, companies) isAllowed := handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool { return privileges.Admin && privileges.Company }) assert.True(t, isAllowed) } func TestPrivilegeHandler_Concurrent_Process_And_Read(t *testing.T) { handler := New(WithBaseURL("base")) var wg sync.WaitGroup // Concurrently add privileges via Process for i := 0; i < 100; i++ { wg.Add(1) companyID := fmt.Sprintf("company-%d", i%10) go func(id string) { defer wg.Done() _, _ = handler.Process(&PrivilegeAdded{ Email: "jim@example.org", CompanyID: id, Privilege: PrivilegeAdmin, }, goamqp.Headers{}) }(companyID) } // Concurrently read privileges while Process is running for i := 0; i < 100; i++ { wg.Add(1) go func() { defer wg.Done() _ = handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool { return privileges.Admin }) }() } wg.Wait() // Verify all companies were added companies := handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool { return privileges.Admin }) sort.Strings(companies) expected := make([]string, 10) for i := 0; i < 10; i++ { expected[i] = fmt.Sprintf("company-%d", i) } sort.Strings(expected) assert.Equal(t, expected, companies) } func TestPrivilegeHandler_Concurrent_Multiple_Operations(t *testing.T) { privileges := ` { "jim@example.org": { "initial-company": { "admin": true, "company": true, "consumer": false, "time": false, "invoicing": false, "accounting": false, "supplier": false, "salary": false } } }` server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(privileges)) })) defer server.Close() baseURL := server.Listener.Addr().String() handler := New(WithBaseURL(fmt.Sprintf("http://%s", baseURL))) var wg sync.WaitGroup // Fetch for i := 0; i < 5; i++ { wg.Add(1) go func() { defer wg.Done() _ = handler.Fetch() }() } // Process PrivilegeAdded for i := 0; i < 20; i++ { wg.Add(1) go func(idx int) { defer wg.Done() _, _ = handler.Process(&PrivilegeAdded{ Email: "jane@example.org", CompanyID: fmt.Sprintf("company-%d", idx%5), Privilege: PrivilegeCompany, }, goamqp.Headers{}) }(i) } // CompaniesByUser reads for i := 0; i < 50; i++ { wg.Add(1) email := "jim@example.org" if i%2 == 0 { email = "jane@example.org" } go func(e string) { defer wg.Done() _ = handler.CompaniesByUser(e, func(privileges CompanyPrivileges) bool { return privileges.Admin || privileges.Company }) }(email) } // IsAllowed reads for i := 0; i < 50; i++ { wg.Add(1) go func() { defer wg.Done() _ = handler.IsAllowed("jim@example.org", "initial-company", func(privileges CompanyPrivileges) bool { return privileges.Admin }) }() } wg.Wait() // Verify final state is consistent jimCompanies := handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool { return privileges.Admin }) assert.Contains(t, jimCompanies, "initial-company") janeCompanies := handler.CompaniesByUser("jane@example.org", func(privileges CompanyPrivileges) bool { return privileges.Company }) sort.Strings(janeCompanies) expectedJane := []string{"company-0", "company-1", "company-2", "company-3", "company-4"} assert.Equal(t, expectedJane, janeCompanies) }