authz_client / test (push) Skipped
authz_client / vulnerabilities (push) Skipped
pre-commit / pre-commit (push) Skipped
authz_client / vulnerabilities (pull_request) Failing after 55s
authz_client / test (pull_request) Successful in 1m4s
pre-commit / pre-commit (pull_request) Successful in 2m16s
Setup() registers one transient consumer per routing key, and each mints its own queue drained by its own goroutine, so User.Added and Privilege.Added for the same company can be processed in either order. Process(*UserAdded) replaced the company entry with empty privileges, so a Privilege.Added handled first lost its privilege until the next Fetch(), which only runs at service start. Create the entry only when it is missing instead, matching authz-service's own aggregate and read view, which both leave an existing user entry alone. This also makes a User.Added redelivery harmless. Seen as authz-service #826 acceptance-test failures: a new company's Admin grant vanished, so company-service's CreateCompany callback waited out its 30s timeout and the company page stayed empty. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XMvdB7bcwn1CrQKM4dCshM
733 lines
19 KiB
Go
733 lines
19 KiB
Go
package client
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"sort"
|
|
"sync"
|
|
"testing"
|
|
|
|
goamqp "codeberg.org/messaging/go-messaging-amqp"
|
|
spec "codeberg.org/messaging/messaging"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestPrivilegeHandler_Process_InvalidType(t *testing.T) {
|
|
handler := New(WithBaseURL("base"))
|
|
|
|
err := handler.Process("abc")
|
|
|
|
assert.EqualError(t, err, "unexpected event type: 'string'")
|
|
}
|
|
|
|
func TestPrivilegeHandler_Process_PrivilegeRemoved(t *testing.T) {
|
|
handler := New(WithBaseURL("base"))
|
|
|
|
err := handler.Process(&PrivilegeAdded{
|
|
Email: "jim@example.org",
|
|
CompanyID: "abc-123",
|
|
Privilege: PrivilegeAdmin,
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
companies := handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool {
|
|
return privileges.Admin
|
|
})
|
|
|
|
assert.Equal(t, []string{"abc-123"}, companies)
|
|
|
|
err = handler.Process(&PrivilegeRemoved{
|
|
Email: "jim@example.org",
|
|
CompanyID: "abc-123",
|
|
Privilege: PrivilegeAdmin,
|
|
})
|
|
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"))
|
|
|
|
err := handler.Process(&UserAdded{
|
|
Email: "jim@example.org",
|
|
CompanyID: "abc-123",
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
err = handler.Process(&UserAdded{
|
|
Email: "jim@example.org",
|
|
CompanyID: "abc-456",
|
|
})
|
|
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)
|
|
|
|
err = handler.Process(&UserRemoved{
|
|
Email: "jim@example.org",
|
|
CompanyID: "abc-123",
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
err = handler.Process(&UserRemoved{
|
|
Email: "jim@example.org",
|
|
CompanyID: "abc-456",
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
companies = handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool {
|
|
return true
|
|
})
|
|
assert.Empty(t, companies)
|
|
}
|
|
|
|
func TestPrivilegeHandler_Process_UserAdded_Keeps_Existing_Privileges(t *testing.T) {
|
|
// Each routing key has its own transient queue, so Privilege.Added can be
|
|
// processed before the User.Added published before it. The privilege must
|
|
// survive either order: nothing re-reads it until the next Fetch at start.
|
|
userAdded := &UserAdded{Email: "jim@example.org", CompanyID: "abc-123"}
|
|
privilegeAdded := &PrivilegeAdded{Email: "jim@example.org", CompanyID: "abc-123", Privilege: PrivilegeAdmin}
|
|
for name, order := range map[string][]any{
|
|
"user added first": {userAdded, privilegeAdded},
|
|
"privilege added first": {privilegeAdded, userAdded},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
handler := New(WithBaseURL("base"))
|
|
for _, event := range order {
|
|
require.NoError(t, handler.Process(event))
|
|
}
|
|
|
|
assert.True(t, handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool {
|
|
return privileges.Admin
|
|
}))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPrivilegeHandler_Process_UserAdded_After_UserRemoved_Starts_Empty(t *testing.T) {
|
|
handler := New(WithBaseURL("base"))
|
|
|
|
assert.NoError(t, handler.Process(&UserAdded{Email: "jim@example.org", CompanyID: "abc-123"}))
|
|
assert.NoError(t, handler.Process(&PrivilegeAdded{
|
|
Email: "jim@example.org",
|
|
CompanyID: "abc-123",
|
|
Privilege: PrivilegeAdmin,
|
|
}))
|
|
assert.NoError(t, handler.Process(&UserRemoved{Email: "jim@example.org", CompanyID: "abc-123"}))
|
|
assert.NoError(t, handler.Process(&UserAdded{Email: "jim@example.org", CompanyID: "abc-123"}))
|
|
|
|
// Membership is back, without the privileges the removal took away.
|
|
assert.Equal(t, []string{"abc-123"}, handler.CompaniesByUser("jim@example.org", func(CompanyPrivileges) bool {
|
|
return true
|
|
}))
|
|
assert.False(t, handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool {
|
|
return privileges.Admin
|
|
}))
|
|
}
|
|
|
|
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"))
|
|
|
|
err := handler.Process(&UserAdded{
|
|
Email: "jim@example.org",
|
|
CompanyID: "abc-123",
|
|
})
|
|
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)
|
|
|
|
err = handler.Process(&UserRemoved{
|
|
Email: "jim@example.org",
|
|
CompanyID: "abc-123",
|
|
})
|
|
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"))
|
|
|
|
err := handler.Process(&PrivilegeAdded{
|
|
Email: "jim@example.org",
|
|
CompanyID: "abc-123",
|
|
Privilege: PrivilegeCompany,
|
|
})
|
|
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"))
|
|
|
|
err := handler.Process(&PrivilegeAdded{
|
|
Email: "jim@example.org",
|
|
CompanyID: "abc-123",
|
|
Privilege: PrivilegeConsumer,
|
|
})
|
|
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,
|
|
})
|
|
|
|
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,
|
|
})
|
|
|
|
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,
|
|
})
|
|
|
|
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,
|
|
})
|
|
|
|
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,
|
|
})
|
|
|
|
result = handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool {
|
|
return privileges.Salary
|
|
})
|
|
|
|
assert.True(t, result)
|
|
}
|
|
|
|
func TestPrivilegeHandler_Fetch_Sends_Authorization_Header_When_APIKey_Set(t *testing.T) {
|
|
var receivedAuth string
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
receivedAuth = r.Header.Get("Authorization")
|
|
_, _ = w.Write([]byte("{}"))
|
|
}))
|
|
defer server.Close()
|
|
|
|
handler := New(
|
|
WithBaseURL(server.URL),
|
|
WithAPIKey("my-secret-key"),
|
|
)
|
|
|
|
err := handler.Fetch()
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "Bearer my-secret-key", receivedAuth)
|
|
}
|
|
|
|
func TestPrivilegeHandler_Fetch_No_Authorization_Header_Without_APIKey(t *testing.T) {
|
|
var receivedAuth string
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
receivedAuth = r.Header.Get("Authorization")
|
|
_, _ = w.Write([]byte("{}"))
|
|
}))
|
|
defer server.Close()
|
|
|
|
handler := New(WithBaseURL(server.URL))
|
|
|
|
err := handler.Fetch()
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, receivedAuth)
|
|
}
|
|
|
|
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,
|
|
})
|
|
}(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,
|
|
})
|
|
}(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)
|
|
}
|
|
|
|
func TestPrivilegeHandler_Setup(t *testing.T) {
|
|
handler := New(WithBaseURL("base"))
|
|
|
|
topology, err := goamqp.CollectTopology("some-service", handler.Setup()...)
|
|
assert.NoError(t, err)
|
|
wiring := map[string]string{}
|
|
for _, e := range topology.Endpoints {
|
|
assert.Equal(t, spec.DirectionConsume, e.Direction)
|
|
assert.True(t, e.Ephemeral, "%s must be a per-replica consumer", e.RoutingKey)
|
|
wiring[e.RoutingKey] = e.MessageType
|
|
}
|
|
// A key wired to the wrong type could turn a revocation into a grant.
|
|
assert.Equal(t, map[string]string{
|
|
"User.Added": "client.UserAdded",
|
|
"User.Removed": "client.UserRemoved",
|
|
"Privilege.Added": "client.PrivilegeAdded",
|
|
"Privilege.Removed": "client.PrivilegeRemoved",
|
|
}, wiring)
|
|
}
|
|
|
|
func TestPrivilegeHandler_process(t *testing.T) {
|
|
ctx := context.Background()
|
|
handler := New(WithBaseURL("base"))
|
|
admin := func(p CompanyPrivileges) bool { return p.Admin }
|
|
|
|
assert.NoError(t, process[UserAdded](handler)(ctx, spec.ConsumableEvent[UserAdded]{
|
|
Payload: UserAdded{Email: "jim@example.org", CompanyID: "abc-123"},
|
|
}))
|
|
assert.False(t, handler.IsAllowed("jim@example.org", "abc-123", admin))
|
|
|
|
assert.NoError(t, process[PrivilegeAdded](handler)(ctx, spec.ConsumableEvent[PrivilegeAdded]{
|
|
Payload: PrivilegeAdded{Email: "jim@example.org", CompanyID: "abc-123", Privilege: PrivilegeAdmin},
|
|
}))
|
|
assert.True(t, handler.IsAllowed("jim@example.org", "abc-123", admin))
|
|
|
|
assert.NoError(t, process[PrivilegeRemoved](handler)(ctx, spec.ConsumableEvent[PrivilegeRemoved]{
|
|
Payload: PrivilegeRemoved{Email: "jim@example.org", CompanyID: "abc-123", Privilege: PrivilegeAdmin},
|
|
}))
|
|
assert.False(t, handler.IsAllowed("jim@example.org", "abc-123", admin))
|
|
|
|
assert.NoError(t, process[UserRemoved](handler)(ctx, spec.ConsumableEvent[UserRemoved]{
|
|
Payload: UserRemoved{Email: "jim@example.org", CompanyID: "abc-123"},
|
|
}))
|
|
assert.Empty(t, handler.CompaniesByUser("jim@example.org", func(CompanyPrivileges) bool { return true }))
|
|
}
|