Skip to content

Conversation

@mauriciozanettisalomao
Copy link
Contributor

Overview

Jira Ticket: https://linuxfoundation.atlassian.net/browse/LFXV2-920

This PR introduces a clean architecture foundation for fine-grained authorization policy evaluation. It creates a reusable abstraction layer for policy handling, starting with committee member visibility but designed to be extended to other policies with minimal adjustments.

Key Changes

  • Domain Layer: New Policy domain entity with validation and helper methods
  • Service Layer: Generic PolicyHandler interface with clean separation of concerns
  • Interface Abstraction: RelationshipSynchronizer interface to decouple policy logic from OpenFGA SDK
  • Integration: Committee handler now supports policy evaluation via policies field

Architecture

Two-Level Policy Relationship Structure

The implementation creates a flexible two-level relationship structure for policy evaluation:

1. Object → Policy: committee:123#visibility_policy@visibility_policy:basic_profile
2. Policy → User Relation: visibility_policy:basic_profile#allows_basic_profile@committee:123#member

This structure allows OpenFGA to evaluate complex policies by traversing relationships:

Object: committee:123
└── relation: visibility_policy → user: visibility_policy:basic_profile
    └── relation: allows_basic_profile → user: committee:123#member
        └── contains: user:user_5678

Clean Architecture Benefits

  1. Reusability: Policy evaluation logic is decoupled and can be applied to any object type (projects, meetings, teams, etc.)
  2. Testability: Domain logic is pure and service layer has clear interface boundaries for mocking
  3. Maintainability: Business logic separated from infrastructure concerns (OpenFGA SDK)
  4. Extensibility: New policies can be added by simply providing the policy configuration, no code changes needed

Future Improvements

As noted in code comments, the RelationshipSynchronizer interface is currently tightly coupled to the OpenFGA client package. This is intentional for this first iteration, but we can gradually refactor to:

  1. Create domain-specific tuple types instead of using client.ClientTupleKey
  2. Further abstract OpenFGA SDK dependencies
  3. Add more policy types (access policies, privacy policies, etc.)
  4. Extend to other object types (projects, meetings, teams)

Migration Path

This PR establishes the foundation. Existing handlers continue to work unchanged. We can now:

  1. Gradually migrate hard OpenFGA SDK dependencies to use clean arch patterns
  2. Add policies to other object types by updating their message handlers
  3. Expand policy types beyond visibility (access control, privacy, etc.)

…synchronization

Jira Ticket: https://linuxfoundation.atlassian.net/browse/LFXV2-920

Assisted by [Claude Code](https://claude.ai/code)

Signed-off-by: Mauricio Zanetti Salomao <mauriciozanetti86@gmail.com>
Jira Ticket: https://linuxfoundation.atlassian.net/browse/LFXV2-920

Assisted by [Claude Code](https://claude.ai/code)

Signed-off-by: Mauricio Zanetti Salomao <mauriciozanetti86@gmail.com>
Copilot AI review requested due to automatic review settings December 24, 2025 12:39
@mauriciozanettisalomao mauriciozanettisalomao requested a review from a team as a code owner December 24, 2025 12:39
@coderabbitai
Copy link

coderabbitai bot commented Dec 24, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds fine-grained policy support: a Policy domain model and tests, a PolicyHandler service and tests, a RelationshipSynchronizer interface, and committee handler integration that stores Policies on committeeStub and evaluates each policy during access sync.

Changes

Cohort / File(s) Summary
Policy Domain Model
internal/domain/policy.go, internal/domain/policy_test.go
New Policy type (Name, Relation, Value) with Validate(), ObjectID(), and UserRelation() methods; unit tests covering validation and string helpers.
Policy Evaluation Service
internal/service/policy_handler.go, internal/service/policy_handler_test.go
Adds PolicyHandler interface and policyHandler implementation plus NewPolicyHandler; validates policies, reads existing tuples, computes writes/deletes for object→policy and policy→user relations, and calls a RelationshipSynchronizer; comprehensive mock-backed tests.
Relationship Synchronizer Interface
internal/service/relationship_sync_service.go
New RelationshipSynchronizer interface with TupleKey, TupleKeyWithoutCondition, ReadObjectTuples, and WriteAndDeleteTuples methods to decouple tuple operations from FGA service.
Committee Handler Integration
handler_committee.go
committeeStub gains Policies []domain.Policy; SyncObjectTuples call now passes an extra "member" parameter; iterates over committee.Policies and invokes PolicyHandler.EvaluatePolicy; added imports for internal/domain and internal/service.
Helm Chart
charts/lfx-v2-fga-sync/Chart.yaml
Bumped chart version from 0.2.9 to 0.3.0.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant CommitteeHandler as "Committee Handler"
    participant PolicyHandler as "Policy Handler"
    participant Sync as "Relationship Synchronizer"
    participant FGA as "FGA Service"

    Client->>CommitteeHandler: Update Committee Access
    CommitteeHandler->>Sync: SyncObjectTuples(..., "member")
    Sync->>FGA: Write/Delete Tuples
    FGA-->>Sync: OK
    Sync-->>CommitteeHandler: OK

    rect rgba(230,245,255,1)
      Note over CommitteeHandler,PolicyHandler: For each policy in committee.Policies
      CommitteeHandler->>PolicyHandler: EvaluatePolicy(ctx, policy, objectID, userRel)
      PolicyHandler->>PolicyHandler: Validate & build tuple keys

      alt Object→Policy link
        PolicyHandler->>Sync: ReadObjectTuples(objectID)
        Sync->>FGA: Read Tuples
        FGA-->>Sync: Tuples
        Sync-->>PolicyHandler: Tuples
        PolicyHandler->>Sync: WriteAndDeleteTuples(writes, deletes)
        Sync->>FGA: Write/Delete Tuples
        FGA-->>Sync: OK
      end

      alt Policy→User relation link
        PolicyHandler->>Sync: ReadObjectTuples(policy.ObjectID())
        Sync->>FGA: Read Tuples
        FGA-->>Sync: Tuples
        Sync-->>PolicyHandler: Tuples
        PolicyHandler->>Sync: WriteAndDeleteTuples(writes, deletes)
        Sync->>FGA: Write/Delete Tuples
        FGA-->>Sync: OK
      end

      PolicyHandler-->>CommitteeHandler: Success / Error
    end

    CommitteeHandler-->>Client: Update Complete / Error
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Linked Issues check ❓ Inconclusive The PR implements foundational architecture for fine-grained authorization with policy abstraction, but does not fully address the acceptance criteria from LFXV2-920 regarding visibility settings with HIDDEN/BASIC_PROFILE levels and comprehensive FGA sync/indexer updates. Clarify whether this PR is Phase 1 establishing core infrastructure or if it should complete LFXV2-920 acceptance criteria including visibility level implementation and full system integration.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main objective of the PR: introducing a policy abstraction layer for fine-grained authorization.
Description check ✅ Passed The description is well-structured, detailed, and directly addresses the changes in the PR, including architecture decisions, implementation details, and future improvements.
Out of Scope Changes check ✅ Passed All changes are aligned with establishing a policy abstraction foundation for authorization. The Helm chart version bump and core architecture additions are appropriately scoped to the PR objectives.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 08879b8 and e6f00be.

📒 Files selected for processing (1)
  • charts/lfx-v2-fga-sync/Chart.yaml
✅ Files skipped from review due to trivial changes (1)
  • charts/lfx-v2-fga-sync/Chart.yaml

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
internal/domain/policy.go (1)

37-42: Consider making UserRelation a standalone function.

This method doesn't reference any fields of the Policy receiver—it's purely a utility that formats objectID#relation. Attaching it to Policy may mislead readers into thinking it uses policy state.

🔎 Alternative: standalone function
// FormatUserRelation returns the user relation string for a given object.
// Format: objectID#relation
// Example: "committee:123#member"
func FormatUserRelation(objectID, relation string) string {
	return fmt.Sprintf("%s#%s", objectID, relation)
}
handler_committee.go (2)

85-98: Consider reusing PolicyHandler instance at handler service level.

Creating a new PolicyHandler for each message with policies adds minor overhead. Since PolicyHandler is stateless (only holds logger and synchronizer references), it could be instantiated once in HandlerService and reused.

🔎 Suggested approach
 type HandlerService struct {
     fgaService  FgaService
+    policyHandler service.PolicyHandler
     // ... other fields
 }

 // In constructor or initialization:
+h.policyHandler = service.NewPolicyHandler(logger, h.fgaService)

 // In committeeUpdateAccessHandler:
-policyEval := service.NewPolicyHandler(logger, h.fgaService)
 for _, policy := range committee.Policies {
-    err = policyEval.EvaluatePolicy(ctx, policy, object)
+    err = h.policyHandler.EvaluatePolicy(ctx, policy, object)

85-98: Policy evaluation errors should be logged with additional context.

When policy evaluation fails, the log includes policy and object, but consider also logging the committee UID for easier debugging and correlation with other log entries.

🔎 Enhanced logging
 err = policyEval.EvaluatePolicy(ctx, policy, object)
 if err != nil {
-    logger.With(errKey, err, "policy", policy, "object", object).ErrorContext(ctx, "failed to evaluate policy")
+    logger.With(errKey, err, "policy", policy, "object", object, "committeeUID", committee.UID).ErrorContext(ctx, "failed to evaluate policy")
     return err
 }
internal/service/policy_handler_test.go (1)

329-337: Consider using errors.Is or checking for wrapped error.

The exact error message comparison ("failed to write policy tuples: write error") is fragile if the message format changes. Using errors.Is or errors.As provides more robust error checking.

🔎 More robust error check
-if !errors.Is(err, expectedError) && err.Error() != "failed to write policy tuples: write error" {
-    t.Errorf("Expected write error, got: %v", err)
-}
+if !errors.Is(err, expectedError) {
+    // Check if the error wraps our expected error
+    var unwrapped error
+    if errors.As(err, &unwrapped) {
+        if !errors.Is(unwrapped, expectedError) {
+            t.Errorf("Expected write error, got: %v", err)
+        }
+    } else {
+        t.Errorf("Expected write error, got: %v", err)
+    }
+}

Alternatively, if the production code wraps errors with fmt.Errorf("...: %w", err), simply checking errors.Is(err, expectedError) should suffice.

internal/service/policy_handler.go (1)

116-116: Hardcoded "member" relation limits reusability.

The "member" relation is hardcoded, but other policy types may need different user relations (e.g., "viewer", "admin"). Consider making this configurable via the Policy struct or as a parameter.

🔎 Suggested approach

Option 1: Add a UserRelationType field to Policy:

type Policy struct {
    Name             string `json:"name"`
    Relation         string `json:"relation"`
    Value            string `json:"value"`
    UserRelationType string `json:"user_relation_type"` // e.g., "member", "viewer"
}

Option 2: Pass the relation type as a parameter to EvaluatePolicy:

EvaluatePolicy(ctx context.Context, policy domain.Policy, objectID string, userRelationType string) error

For now, adding a comment documenting the "member" assumption would help:

// Default to "member" relation for committee visibility policies.
// TODO: Make configurable when supporting other policy types.
userRelation := policy.UserRelation(objectID, "member")
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 02677a2 and a6ba574.

📒 Files selected for processing (6)
  • handler_committee.go
  • internal/domain/policy.go
  • internal/domain/policy_test.go
  • internal/service/policy_handler.go
  • internal/service/policy_handler_test.go
  • internal/service/relationship_sync_service.go
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.go: Use handler functions dedicated to each message type (accessCheckHandler, projectUpdateHandler, projectDeleteHandler, committeeUpdateAccessHandler, etc.) following the handler pattern architecture
Implement cache strategy using Base32-encoded relation tuples as cache keys (e.g., rel.{encoded-relation}) and JSON values with allowed boolean and created_at timestamp
Use timestamp-based cache invalidation with configurable staleness tolerance and implement fallback to direct OpenFGA queries on cache miss
Implement structured logging with context, graceful degradation on cache miss, message replies with error details, and ensure service continues running on individual message failures
Use preallocated slices to reduce garbage collection, batch OpenFGA operations up to 100 tuples per request, employ cache-first approach, and use efficient string parsing with bytes.Cut
Implement health endpoints for Kubernetes-ready liveness and readiness probes at /livez and /readyz paths
Expose expvar metrics at /debug/vars endpoint including cache hits, misses, and stale hits for monitoring
Use structured JSON logging for observability across the service

Files:

  • internal/domain/policy_test.go
  • internal/domain/policy.go
  • internal/service/relationship_sync_service.go
  • handler_committee.go
  • internal/service/policy_handler.go
  • internal/service/policy_handler_test.go
**/*_test.go

📄 CodeRabbit inference engine (CLAUDE.md)

Write test cases in *_test.go files containing unit tests for each handler, with full test structure including integration tests using docker-compose.test.yml

Files:

  • internal/domain/policy_test.go
  • internal/service/policy_handler_test.go
🧬 Code graph analysis (4)
internal/domain/policy_test.go (1)
internal/domain/policy.go (1)
  • Policy (9-13)
handler_committee.go (2)
internal/domain/policy.go (1)
  • Policy (9-13)
internal/service/policy_handler.go (1)
  • NewPolicyHandler (148-153)
internal/service/policy_handler.go (2)
internal/domain/policy.go (1)
  • Policy (9-13)
internal/service/relationship_sync_service.go (1)
  • RelationshipSynchronizer (18-23)
internal/service/policy_handler_test.go (2)
internal/service/policy_handler.go (2)
  • NewPolicyHandler (148-153)
  • PolicyHandler (16-18)
internal/domain/policy.go (1)
  • Policy (9-13)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Agent
🔇 Additional comments (13)
internal/domain/policy.go (2)

8-13: LGTM!

Clean domain model with appropriate JSON tags for serialization. The struct captures the essential policy attributes needed for OpenFGA relationship construction.


15-28: LGTM!

Validation logic is clear and returns specific error messages for each missing field. The validation order (Name → Value → Relation) is consistent with field declaration order.

internal/domain/policy_test.go (4)

10-80: LGTM!

Comprehensive table-driven tests for Validate() covering all field combinations. Error message assertions ensure consistent user-facing errors.


82-129: LGTM!

Good coverage of ObjectID() including edge cases with empty values and special characters. The ":" output for empty values documents current behavior even if not recommended for production use.


131-183: LGTM!

Tests verify UserRelation() formatting correctly. Edge case tests for empty objectID and empty relation document the method's behavior with invalid inputs.


185-212: LGTM!

Integration test validates the typical usage flow: validate → get object ID → get user relation. Uses realistic UUID format for committee ID.

internal/service/relationship_sync_service.go (1)

13-23: LGTM - pragmatic interface design with documented trade-off.

The interface successfully decouples PolicyHandler from FgaService while acknowledging the SDK type coupling. The inline comment (line 17) documenting the anti-pattern and intent to refactor is good practice for future maintainability.

internal/service/policy_handler_test.go (3)

23-66: LGTM!

Well-designed mock with call tracking, error injection, and captured write/delete tuples. This enables thorough verification of PolicyHandler behavior.


140-198: LGTM!

Thorough verification of the happy path: validates tuple structure, write count, delete count, and ReadObjectTuples call count. The expected tuple assertions ensure the two-level relationship structure is correctly formed.


408-418: LGTM!

Constructor test verifies non-nil return and interface compliance via compile-time check.

internal/service/policy_handler.go (2)

25-47: Excellent documentation!

The detailed ASCII diagram and examples clearly explain the two-level relationship structure. This significantly aids maintainability and onboarding for developers unfamiliar with the OpenFGA tuple model.


58-93: LGTM on idempotent tuple sync logic.

The checkTuple helper correctly:

  • Identifies exact matches to skip redundant writes
  • Marks conflicting tuples (same user, different relation) for deletion
  • Prepares new tuples only when needed

This ensures idempotent behavior on repeated calls.

handler_committee.go (1)

78-83: The new "member" parameter in SyncObjectTuples is correctly used and all callers are consistent.

The function signature supports variadic excludeRelations ...string, allowing zero or more relation names to be passed as arguments. The call in handler_committee.go with "member" as the fourth argument is valid, and other handlers already use this pattern with 0-3 excludeRelations arguments (e.g., handler_past_meeting.go passes 3 relations: RelationOrganizer, RelationHost, RelationInvitee; handler_meeting.go passes 2: RelationParticipant, RelationHost). All callers are properly aligned with the function signature.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a clean architecture foundation for fine-grained authorization policy evaluation in the FGA sync service. It establishes a reusable abstraction layer that enables policy-based access control through a two-level relationship structure in OpenFGA, starting with committee member visibility policies.

Key Changes:

  • Introduces a domain-driven Policy entity with validation and helper methods for constructing OpenFGA tuple identifiers
  • Creates a PolicyHandler service interface with clean separation from OpenFGA SDK dependencies via the RelationshipSynchronizer interface
  • Integrates policy evaluation into the committee handler, allowing committees to specify policies that control member visibility

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
internal/domain/policy.go Defines the Policy domain entity with validation and helper methods for OpenFGA tuple construction
internal/domain/policy_test.go Comprehensive unit tests for the Policy domain entity covering validation, object ID generation, and user relation formatting
internal/service/relationship_sync_service.go Defines the RelationshipSynchronizer interface to decouple policy logic from OpenFGA SDK implementation
internal/service/policy_handler.go Implements the PolicyHandler service that evaluates policies and creates two-level tuple relationships in OpenFGA
internal/service/policy_handler_test.go Comprehensive unit tests for PolicyHandler covering validation, tuple management, conflict resolution, and error scenarios
handler_committee.go Updates committee handler to support policy evaluation through the new policies field and excludes member relations from standard sync

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Jira Ticket: https://linuxfoundation.atlassian.net/browse/LFXV2-920

Reviewed with [GitHub Copilot](https://github.com/features/copilot)

Signed-off-by: Mauricio Zanetti Salomao <mauriciozanetti86@gmail.com>
…ttee policies

Jira Ticket: https://linuxfoundation.atlassian.net/browse/LFXV2-920

Reviewed with [GitHub Copilot](https://github.com/features/copilot)

Signed-off-by: Mauricio Zanetti Salomao <mauriciozanetti86@gmail.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
handler_committee.go (1)

85-96: Consider caching PolicyHandler instance for efficiency.

Creating a new PolicyHandler on every committee update adds unnecessary allocation overhead. Since PolicyHandler has no mutable state, consider creating it once (e.g., as a field in HandlerService) and reusing it across invocations.

🔎 Example refactor to reuse PolicyHandler

Add a field to HandlerService:

type HandlerService struct {
    fgaService    *FGAService
    policyHandler service.PolicyHandler
    // ... other fields
}

Initialize it once during service construction:

func NewHandlerService(fgaService *FGAService, logger *slog.Logger) *HandlerService {
    return &HandlerService{
        fgaService:    fgaService,
        policyHandler: service.NewPolicyHandler(logger, fgaService),
        // ... other fields
    }
}

Then reuse it here:

 if len(committee.Policies) > 0 {
-    policyEval := service.NewPolicyHandler(logger, h.fgaService)
-
     // Evaluate each policy associated with the committee
     for _, policy := range committee.Policies {
-        errEvaluatePolicy := policyEval.EvaluatePolicy(ctx, policy, object, "member")
+        errEvaluatePolicy := h.policyHandler.EvaluatePolicy(ctx, policy, object, "member")
         if errEvaluatePolicy != nil {
             logger.With(errKey, errEvaluatePolicy, "policy", policy, "object", object).ErrorContext(ctx, "failed to evaluate policy")
             return errEvaluatePolicy
         }
     }
 }
internal/service/policy_handler.go (1)

96-146: LGTM! Correct two-level policy relationship implementation.

The policy evaluation correctly implements the two-level relationship structure described in the documentation:

  1. Object → Policy link
  2. Policy → User Relation link

The conditional write optimization (lines 133-143) ensures tuples are only written when changes are needed.

Minor: Misleading comment at line 117.

The comment says "Default to 'member' relation" but userObjectRelation is passed as a parameter without any defaulting logic. Consider updating the comment for clarity:

-	// Format: policy.Name:policy.Value -> policy.Relation -> objectID#userRelation
-	// Example: visibility_policy:basic_profile#allows_basic_profile@committee:C#member
-	userRelation := policy.UserRelation(objectID, userObjectRelation) // Default to "member" relation
+	// Format: policy.Name:policy.Value -> policy.Relation -> objectID#userObjectRelation
+	// Example: visibility_policy:basic_profile#allows_basic_profile@committee:C#member
+	userRelation := policy.UserRelation(objectID, userObjectRelation)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 625f846 and 4db32b4.

📒 Files selected for processing (3)
  • handler_committee.go
  • internal/service/policy_handler.go
  • internal/service/policy_handler_test.go
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.go: Use handler functions dedicated to each message type (accessCheckHandler, projectUpdateHandler, projectDeleteHandler, committeeUpdateAccessHandler, etc.) following the handler pattern architecture
Implement cache strategy using Base32-encoded relation tuples as cache keys (e.g., rel.{encoded-relation}) and JSON values with allowed boolean and created_at timestamp
Use timestamp-based cache invalidation with configurable staleness tolerance and implement fallback to direct OpenFGA queries on cache miss
Implement structured logging with context, graceful degradation on cache miss, message replies with error details, and ensure service continues running on individual message failures
Use preallocated slices to reduce garbage collection, batch OpenFGA operations up to 100 tuples per request, employ cache-first approach, and use efficient string parsing with bytes.Cut
Implement health endpoints for Kubernetes-ready liveness and readiness probes at /livez and /readyz paths
Expose expvar metrics at /debug/vars endpoint including cache hits, misses, and stale hits for monitoring
Use structured JSON logging for observability across the service

Files:

  • handler_committee.go
  • internal/service/policy_handler_test.go
  • internal/service/policy_handler.go
**/*_test.go

📄 CodeRabbit inference engine (CLAUDE.md)

Write test cases in *_test.go files containing unit tests for each handler, with full test structure including integration tests using docker-compose.test.yml

Files:

  • internal/service/policy_handler_test.go
🧬 Code graph analysis (3)
handler_committee.go (2)
internal/domain/policy.go (1)
  • Policy (9-13)
internal/service/policy_handler.go (1)
  • NewPolicyHandler (149-154)
internal/service/policy_handler_test.go (2)
internal/service/policy_handler.go (2)
  • NewPolicyHandler (149-154)
  • PolicyHandler (16-18)
internal/domain/policy.go (1)
  • Policy (9-13)
internal/service/policy_handler.go (2)
internal/domain/policy.go (1)
  • Policy (9-13)
internal/service/relationship_sync_service.go (1)
  • RelationshipSynchronizer (18-27)
🔇 Additional comments (7)
handler_committee.go (2)

13-14: LGTM! Imports support policy evaluation.

The new imports are necessary for the Policy domain type and PolicyHandler service integration.


25-25: LGTM! Policies field enables policy-based authorization.

The field addition properly supports the policy abstraction architecture described in the PR objectives.

internal/service/policy_handler_test.go (1)

1-418: Excellent test coverage for PolicyHandler!

The test suite is comprehensive and well-structured:

  • Thorough validation error coverage (empty name, value, relation, objectID)
  • Happy path verification with no existing tuples
  • Idempotency check with existing tuples
  • Conflict resolution with tuple deletions
  • Error propagation for both read and write operations
  • Multi-policy scenarios validating reusability
  • Constructor and interface compliance verification

The mock implementation properly tracks calls, writes, deletes, and simulates errors, enabling thorough behavior validation without OpenFGA dependencies.

internal/service/policy_handler.go (4)

15-23: LGTM! Clean interface design.

The PolicyHandler interface provides a focused API for policy evaluation, and the policyHandler struct maintains minimal, immutable state, making it safe for concurrent use and reuse across multiple invocations.


48-56: LGTM! Proper validation before processing.

The validation logic correctly delegates to the domain model's validation method and explicitly checks for required parameters before proceeding with policy evaluation.


58-94: LGTM! Idempotent tuple management.

The checkTuple helper correctly implements idempotent tuple synchronization:

  • Detects exact matches and avoids redundant writes
  • Identifies conflicts (same user, different relation) and marks them for deletion
  • Prepares new tuples only when needed

The logic properly handles multiple conflicting tuples by adding all of them to the deletion list.


148-154: LGTM! Clean constructor following dependency injection.

The constructor properly follows the dependency injection pattern and returns the interface type, which enables testing and maintains good separation of concerns.

Jira Ticket: https://linuxfoundation.atlassian.net/browse/LFXV2-920

Assisted by [Claude Code](https://claude.ai/code)

Signed-off-by: Mauricio Zanetti Salomao <mauriciozanetti86@gmail.com>
…ittee access handler

Jira Ticket: https://linuxfoundation.atlassian.net/browse/LFXV2-920

Signed-off-by: Mauricio Zanetti Salomao <mauriciozanetti86@gmail.com>
…bility

Jira Ticket: https://linuxfoundation.atlassian.net/browse/LFXV2-920

Signed-off-by: Mauricio Zanetti Salomao <mauriciozanetti86@gmail.com>
Jira Ticket: https://linuxfoundation.atlassian.net/browse/LFXV2-920

Signed-off-by: Mauricio Zanetti Salomao <mauriciozanetti86@gmail.com>
Copy link
Contributor

@andrest50 andrest50 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants