Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5b49b36a32 | |||
| a06bae1da9 | |||
| 90084cc3a4 | |||
| 7825fa17a6 | |||
|
73f854ba06
|
|||
| 5b3527439f | |||
|
681afe2626
|
|||
| 2e1eb327e0 | |||
| 622d907e03 | |||
| 4b38ce4f0f | |||
| b82e15c49b | |||
| f0ea0d7d26 | |||
| 26de10c2b9 | |||
| 3865b1b5f7 | |||
|
cd84a51f91
|
|||
| eab39dc818 | |||
| 524cad9180 | |||
| b339804535 | |||
| d2ed9ed12a | |||
| f521fb29c9 | |||
| 123dd2a4c2 | |||
| c5943b41ec | |||
| 5644b061c0 | |||
| 8330219579 | |||
| 425013f115 | |||
| 7f3b78b000 | |||
| ab8a9809d5 | |||
| 9ef9084ffa | |||
| e48c5b3bb9 | |||
| 4421bcfbeb | |||
| 18748ceaad | |||
| 14d32b3b51 | |||
| d571e92a0b | |||
| d355edd642 | |||
| abd34b334a | |||
| da73907913 | |||
| becde50685 | |||
| a84a14a0d3 | |||
| 707e26b420 | |||
| ffa2eca348 | |||
| 76fc782c96 | |||
| 4d3147c65c | |||
| 6643990160 | |||
| 20d69f9c19 | |||
| d327307539 | |||
| 5dce8a0f2b | |||
| 6f6272cb02 | |||
| 7eddad8d4b | |||
|
1fd3ae5123
|
|||
| 247c04a710 | |||
| 37f6c63025 | |||
| 215a9ed976 | |||
| 006ebd101e | |||
| 600653518c | |||
| c95cd1c80a | |||
| 881a6f0e3c | |||
| 84939fa04b | |||
| 0821cbb6eb | |||
| 0cb8363ab1 | |||
| 8a440bd28c | |||
| 13461b43e3 | |||
| 49100894e9 | |||
| 7818f97a7c | |||
| 66c429bde1 | |||
| 585fa5dfa4 | |||
| 82cca9b09c | |||
| bcbddac138 | |||
| e62257d933 | |||
| f45918bac8 | |||
| 77ac58202a | |||
| 257a97f191 | |||
| 32e8127273 | |||
| 223f65396d | |||
| 01d4a4bc9f | |||
| 1038cff1d9 | |||
| f961bf91f7 | |||
| 721cb1be91 | |||
| 2f570a0638 | |||
| ee52c50e76 | |||
| 675ac0338f | |||
| f708a18960 | |||
| a8ba5635e3 | |||
|
4efc6572ee
|
@@ -0,0 +1,30 @@
|
|||||||
|
name: authz_client
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version: 'stable'
|
||||||
|
- name: Run tests
|
||||||
|
run: go test -race -coverprofile=coverage.txt ./...
|
||||||
|
|
||||||
|
vulnerabilities:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version: 'stable'
|
||||||
|
- name: Check vulnerabilities
|
||||||
|
run: |
|
||||||
|
go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||||
|
govulncheck ./...
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
name: pre-commit
|
||||||
|
permissions: read-all
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pre-commit:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
SKIP: no-commit-to-branch
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version: stable
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.14'
|
||||||
|
- name: Install goimports
|
||||||
|
run: go install golang.org/x/tools/cmd/goimports@latest
|
||||||
|
- uses: pre-commit/action@v3.0.1
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
uses: unboundsoftware/shared-workflows/.gitea/workflows/Release.yml@main
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
.idea
|
.idea
|
||||||
|
.claude
|
||||||
/release
|
/release
|
||||||
|
coverage.txt
|
||||||
|
|||||||
+2
-2
@@ -7,7 +7,7 @@ include:
|
|||||||
- project: unboundsoftware/ci-templates
|
- project: unboundsoftware/ci-templates
|
||||||
file: Pre-Commit-Go.gitlab-ci.yml
|
file: Pre-Commit-Go.gitlab-ci.yml
|
||||||
|
|
||||||
image: amd64/golang:1.25.3@sha256:69d10098be2e990bb1d987daec0e36d18ad287e139450dc7d98a0ded3498888d
|
image: amd64/golang:1.25.5@sha256:ad03ba93327b8a6143b49373790b5d92c28067bdb814418509466122ee9c9e63
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- deps
|
- deps
|
||||||
@@ -32,7 +32,7 @@ test:
|
|||||||
|
|
||||||
vulnerabilities:
|
vulnerabilities:
|
||||||
stage: test
|
stage: test
|
||||||
image: amd64/golang:1.25.3@sha256:69d10098be2e990bb1d987daec0e36d18ad287e139450dc7d98a0ded3498888d
|
image: amd64/golang:1.25.5@sha256:ad03ba93327b8a6143b49373790b5d92c28067bdb814418509466122ee9c9e63
|
||||||
script:
|
script:
|
||||||
- go install golang.org/x/vuln/cmd/govulncheck@latest
|
- go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||||
- govulncheck ./...
|
- govulncheck ./...
|
||||||
|
|||||||
+3
-10
@@ -10,13 +10,6 @@ repos:
|
|||||||
args:
|
args:
|
||||||
- --allow-multiple-documents
|
- --allow-multiple-documents
|
||||||
- id: check-added-large-files
|
- id: check-added-large-files
|
||||||
- repo: https://gitlab.com/devopshq/gitlab-ci-linter
|
|
||||||
rev: v1.0.6
|
|
||||||
hooks:
|
|
||||||
- id: gitlab-ci-linter
|
|
||||||
args:
|
|
||||||
- --project
|
|
||||||
- unboundsoftware/shiny/authz_client
|
|
||||||
- repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
|
- repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
|
||||||
rev: v9.23.0
|
rev: v9.23.0
|
||||||
hooks:
|
hooks:
|
||||||
@@ -30,17 +23,17 @@ repos:
|
|||||||
- id: go-imports
|
- id: go-imports
|
||||||
args:
|
args:
|
||||||
- -local
|
- -local
|
||||||
- gitlab.com/unboundsoftware/shiny/authz_client
|
- git.unbound.se/shiny/authz_client
|
||||||
- repo: https://github.com/lietu/go-pre-commit
|
- repo: https://github.com/lietu/go-pre-commit
|
||||||
rev: v1.0.0
|
rev: v1.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: go-test
|
- id: go-test
|
||||||
- id: gofumpt
|
- id: gofumpt
|
||||||
- repo: https://github.com/golangci/golangci-lint
|
- repo: https://github.com/golangci/golangci-lint
|
||||||
rev: v2.6.0
|
rev: v2.8.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: golangci-lint-full
|
- id: golangci-lint-full
|
||||||
- repo: https://github.com/gitleaks/gitleaks
|
- repo: https://github.com/gitleaks/gitleaks
|
||||||
rev: v8.28.0
|
rev: v8.30.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: gitleaks
|
- id: gitleaks
|
||||||
|
|||||||
@@ -2,6 +2,47 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [0.4.0] - 2026-01-09
|
||||||
|
|
||||||
|
### 🚀 Features
|
||||||
|
|
||||||
|
- Migrate from GitLab CI to Gitea Actions
|
||||||
|
|
||||||
|
### 🚜 Refactor
|
||||||
|
|
||||||
|
- Update module path to new repository location
|
||||||
|
|
||||||
|
### 📚 Documentation
|
||||||
|
|
||||||
|
- Add CLAUDE.md for Claude Code integration
|
||||||
|
|
||||||
|
### 🧪 Testing
|
||||||
|
|
||||||
|
- Add concurrent fetch and read tests for privileges
|
||||||
|
|
||||||
|
### ⚙️ Miscellaneous Tasks
|
||||||
|
|
||||||
|
- *(deps)* Update golang:1.25.3 docker digest to 9ac0edc
|
||||||
|
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.6.1
|
||||||
|
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.29.0
|
||||||
|
- *(deps)* Update dependency go to v1.25.4
|
||||||
|
- *(deps)* Update golang docker tag to v1.25.4
|
||||||
|
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.6.2
|
||||||
|
- *(deps)* Update golang:1.25.4 docker digest to efe81fa
|
||||||
|
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.29.1
|
||||||
|
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.30.0
|
||||||
|
- *(deps)* Update dependency go to v1.25.5
|
||||||
|
- *(deps)* Update golang docker tag to v1.25.5
|
||||||
|
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.7.0
|
||||||
|
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.7.1
|
||||||
|
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.7.2
|
||||||
|
- *(deps)* Update golang:1.25.5 docker digest to 0c27bcf
|
||||||
|
- *(deps)* Update golang:1.25.5 docker digest to ad03ba9
|
||||||
|
- *(deps)* Update actions/setup-go action to v6
|
||||||
|
- *(deps)* Update actions/checkout action to v6
|
||||||
|
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.8.0
|
||||||
|
- Add pre-commit and release workflows
|
||||||
|
|
||||||
## [0.3.1] - 2025-11-02
|
## [0.3.1] - 2025-11-02
|
||||||
|
|
||||||
### 🐛 Bug Fixes
|
### 🐛 Bug Fixes
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# authz_client
|
||||||
|
|
||||||
|
Shared Go library for authorization service client integration.
|
||||||
|
|
||||||
|
## Shared Documentation
|
||||||
|
|
||||||
|
@../docs/claude/architecture.md
|
||||||
|
@../docs/claude/go-services.md
|
||||||
|
@../docs/claude/conventions.md
|
||||||
|
|
||||||
|
## Library Information
|
||||||
|
|
||||||
|
### Purpose
|
||||||
|
|
||||||
|
Provides a client for the authz-service, handling privilege management for users across companies. Used by all microservices that need to check user permissions.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
```go
|
||||||
|
import client "git.unbound.se/shiny/authz_client"
|
||||||
|
|
||||||
|
// Create handler with options
|
||||||
|
handler := client.New(client.WithBaseURL("http://authz-service"))
|
||||||
|
|
||||||
|
// Check user privileges
|
||||||
|
privileges := handler.Get(email, companyID)
|
||||||
|
if privileges.Invoicing {
|
||||||
|
// User has invoicing privileges
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Privileges
|
||||||
|
|
||||||
|
The `CompanyPrivileges` struct contains permission flags:
|
||||||
|
- `Admin` - Administrative access
|
||||||
|
- `Company` - Company management
|
||||||
|
- `Consumer` - Consumer/customer access
|
||||||
|
- `Time` - Time tracking
|
||||||
|
- `Invoicing` - Invoice management
|
||||||
|
- `Accounting` - Accounting access
|
||||||
|
- `Supplier` - Supplier management
|
||||||
|
- `Salary` - Salary/payroll access
|
||||||
|
|
||||||
|
### Event Handling
|
||||||
|
|
||||||
|
Implements `goamqp` message handlers to receive privilege update events from the authz-service, keeping the local privilege cache up-to-date.
|
||||||
@@ -1,4 +1 @@
|
|||||||
# Shiny authz-client
|
# Shiny authz-client
|
||||||
|
|
||||||
[](https://gitlab.com/unboundsoftware/shiny/authz_client/commits/main)
|
|
||||||
[](https://codecov.io/gl/unboundsoftware:shiny/authz_client)
|
|
||||||
|
|||||||
+283
@@ -5,6 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"sort"
|
"sort"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/sparetimecoders/goamqp"
|
"github.com/sparetimecoders/goamqp"
|
||||||
@@ -332,3 +333,285 @@ func TestPrivilegeHandler_Fetch_Valid(t *testing.T) {
|
|||||||
}
|
}
|
||||||
assert.Equal(t, expectedPrivileges, handler.privileges)
|
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)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user