diff --git a/supply-api/internal/iam/repository/iam_repository.go b/supply-api/internal/iam/repository/iam_repository.go index 578e1667..b58ad45e 100644 --- a/supply-api/internal/iam/repository/iam_repository.go +++ b/supply-api/internal/iam/repository/iam_repository.go @@ -90,6 +90,13 @@ func iamNullableIP(value string) interface{} { return value } +func iamNullableInt64Param(value int64) interface{} { + if value == 0 { + return nil + } + return value +} + // Ensure interfaces var _ IAMRepository = (*PostgresIAMRepository)(nil) @@ -427,7 +434,7 @@ func (r *PostgresIAMRepository) AssignRole(ctx context.Context, userRole *model. _, err = r.pool.Exec(ctx, ` INSERT INTO iam_user_roles (user_id, role_id, tenant_id, is_active, granted_by, expires_at, request_id) VALUES ($1, $2, $3, $4, $5, $6, $7) - `, userRole.UserID, userRole.RoleID, userRole.TenantID, true, userRole.GrantedBy, userRole.ExpiresAt, userRole.RequestID) + `, userRole.UserID, userRole.RoleID, userRole.TenantID, true, iamNullableInt64Param(userRole.GrantedBy), userRole.ExpiresAt, userRole.RequestID) if err != nil { if strings.Contains(err.Error(), "duplicate key") || strings.Contains(err.Error(), "unique constraint") { diff --git a/supply-api/internal/iam/repository/repository_test.go b/supply-api/internal/iam/repository/repository_test.go index cb81e9f9..1fe73765 100644 --- a/supply-api/internal/iam/repository/repository_test.go +++ b/supply-api/internal/iam/repository/repository_test.go @@ -329,3 +329,53 @@ func TestUpdateRolePreservesExplicitUpdatedIP(t *testing.T) { t.Fatalf("expected explicit updated_ip to be preserved, got %#v", db.execArgs[4]) } } + +func TestAssignRoleStoresZeroGrantedByAsNil(t *testing.T) { + db := &stubIAMDB{ + queryRow: stubIAMRow{err: pgx.ErrNoRows}, + execTag: pgconn.NewCommandTag("INSERT 0 1"), + } + repo := newPostgresIAMRepositoryWithDB(db) + + err := repo.AssignRole(context.Background(), &model.UserRoleMapping{ + UserID: 7, + RoleID: 9, + TenantID: 42, + GrantedBy: 0, + RequestID: "req-assign-1", + }) + if err != nil { + t.Fatalf("AssignRole() error = %v", err) + } + if len(db.execArgs) != 7 { + t.Fatalf("unexpected exec args length: got=%d want=7", len(db.execArgs)) + } + if db.execArgs[4] != nil { + t.Fatalf("expected zero granted_by to be stored as nil, got %#v", db.execArgs[4]) + } +} + +func TestAssignRolePreservesExplicitGrantedBy(t *testing.T) { + db := &stubIAMDB{ + queryRow: stubIAMRow{err: pgx.ErrNoRows}, + execTag: pgconn.NewCommandTag("INSERT 0 1"), + } + repo := newPostgresIAMRepositoryWithDB(db) + + err := repo.AssignRole(context.Background(), &model.UserRoleMapping{ + UserID: 7, + RoleID: 9, + TenantID: 42, + GrantedBy: 1001, + RequestID: "req-assign-2", + }) + if err != nil { + t.Fatalf("AssignRole() error = %v", err) + } + if len(db.execArgs) != 7 { + t.Fatalf("unexpected exec args length: got=%d want=7", len(db.execArgs)) + } + if db.execArgs[4] != int64(1001) { + t.Fatalf("expected explicit granted_by to be preserved, got %#v", db.execArgs[4]) + } +}