Ctrl K
ComunidadGuías…

internal/handler/organization_handler_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package handler

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/johnzastrow/actalog/internal/domain"
	"github.com/johnzastrow/actalog/internal/service"
)

func TestOrganizationHandler_CreateOrganization_Unauthorized(t *testing.T) {
	handler := &OrganizationHandler{}

	req := createTestRequest(http.MethodPost, "/api/admin/organizations", `{"name": "Test Org"}`)
	rr := httptest.NewRecorder()

	handler.CreateOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusUnauthorized)
	assertBodyContains(t, rr, "Unauthorized")
}

func TestOrganizationHandler_CreateOrganization_InvalidJSON(t *testing.T) {
	handler := &OrganizationHandler{}

	req := createAuthenticatedRequest(http.MethodPost, "/api/admin/organizations", "{bad json", 1, "admin@example.com", "admin")
	rr := httptest.NewRecorder()

	handler.CreateOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusBadRequest)
	assertBodyContains(t, rr, "Invalid request body")
}

func TestOrganizationHandler_CreateOrganization_MissingName(t *testing.T) {
	handler := &OrganizationHandler{}

	req := createAuthenticatedRequest(http.MethodPost, "/api/admin/organizations", `{"description": "Test"}`, 1, "admin@example.com", "admin")
	rr := httptest.NewRecorder()

	handler.CreateOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusBadRequest)
	assertBodyContains(t, rr, "Organization name is required")
}

func TestOrganizationHandler_GetOrganization_InvalidID(t *testing.T) {
	handler := &OrganizationHandler{}

	// chi.URLParam returns empty string without router context
	req := createTestRequest(http.MethodGet, "/api/admin/organizations/abc", "")
	rr := httptest.NewRecorder()

	handler.GetOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusBadRequest)
	assertBodyContains(t, rr, "Invalid organization ID")
}

func TestOrganizationHandler_UpdateOrganization_Unauthorized(t *testing.T) {
	handler := &OrganizationHandler{}

	req := createTestRequest(http.MethodPut, "/api/admin/organizations/1", `{"name": "Updated"}`)
	rr := httptest.NewRecorder()

	handler.UpdateOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusUnauthorized)
	assertBodyContains(t, rr, "Unauthorized")
}

func TestOrganizationHandler_UpdateOrganization_InvalidID(t *testing.T) {
	handler := &OrganizationHandler{}

	req := createAuthenticatedRequest(http.MethodPut, "/api/admin/organizations/abc", `{"name": "Updated"}`, 1, "admin@example.com", "admin")
	rr := httptest.NewRecorder()

	handler.UpdateOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusBadRequest)
	assertBodyContains(t, rr, "Invalid organization ID")
}

func TestOrganizationHandler_UpdateOrganization_InvalidJSON(t *testing.T) {
	handler := &OrganizationHandler{}

	req := createAuthenticatedRequest(http.MethodPut, "/api/admin/organizations/1", "{bad json", 1, "admin@example.com", "admin")
	rr := httptest.NewRecorder()

	handler.UpdateOrganization(rr, req)

	// Without chi router context, URL param is empty -> Invalid organization ID
	assertStatusCode(t, rr, http.StatusBadRequest)
}

func TestOrganizationHandler_DeleteOrganization_Unauthorized(t *testing.T) {
	handler := &OrganizationHandler{}

	req := createTestRequest(http.MethodDelete, "/api/admin/organizations/1", "")
	rr := httptest.NewRecorder()

	handler.DeleteOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusUnauthorized)
	assertBodyContains(t, rr, "Unauthorized")
}

func TestOrganizationHandler_DeleteOrganization_InvalidID(t *testing.T) {
	handler := &OrganizationHandler{}

	req := createAuthenticatedRequest(http.MethodDelete, "/api/admin/organizations/abc", "", 1, "admin@example.com", "admin")
	rr := httptest.NewRecorder()

	handler.DeleteOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusBadRequest)
	assertBodyContains(t, rr, "Invalid organization ID")
}

func TestOrganizationHandler_AssignUserToOrganization_Unauthorized(t *testing.T) {
	handler := &OrganizationHandler{}

	req := createTestRequest(http.MethodPost, "/api/admin/users/1/organization", `{"organization_id": 1}`)
	rr := httptest.NewRecorder()

	handler.AssignUserToOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusUnauthorized)
	assertBodyContains(t, rr, "Unauthorized")
}

func TestOrganizationHandler_AssignUserToOrganization_InvalidUserID(t *testing.T) {
	handler := &OrganizationHandler{}

	req := createAuthenticatedRequest(http.MethodPost, "/api/admin/users/abc/organization", `{"organization_id": 1}`, 1, "admin@example.com", "admin")
	rr := httptest.NewRecorder()

	handler.AssignUserToOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusBadRequest)
	assertBodyContains(t, rr, "Invalid user ID")
}

func TestOrganizationHandler_AssignUserToOrganization_InvalidJSON(t *testing.T) {
	handler := &OrganizationHandler{}

	req := createAuthenticatedRequest(http.MethodPost, "/api/admin/users/1/organization", "{bad json", 1, "admin@example.com", "admin")
	rr := httptest.NewRecorder()

	handler.AssignUserToOrganization(rr, req)

	// Without chi router context, URL param is empty -> Invalid user ID
	assertStatusCode(t, rr, http.StatusBadRequest)
}

func TestOrganizationHandler_AssignUserToOrganization_MissingOrgID(t *testing.T) {
	handler := &OrganizationHandler{}

	req := createAuthenticatedRequest(http.MethodPost, "/api/admin/users/1/organization", `{}`, 1, "admin@example.com", "admin")
	rr := httptest.NewRecorder()

	handler.AssignUserToOrganization(rr, req)

	// Without chi router context, URL param is empty -> Invalid user ID
	assertStatusCode(t, rr, http.StatusBadRequest)
}

func TestOrganizationHandler_RemoveUserFromOrganization_Unauthorized(t *testing.T) {
	handler := &OrganizationHandler{}

	req := createTestRequest(http.MethodDelete, "/api/admin/users/1/organization/1", "")
	rr := httptest.NewRecorder()

	handler.RemoveUserFromOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusUnauthorized)
	assertBodyContains(t, rr, "Unauthorized")
}

func TestOrganizationHandler_RemoveUserFromOrganization_InvalidUserID(t *testing.T) {
	handler := &OrganizationHandler{}

	req := createAuthenticatedRequest(http.MethodDelete, "/api/admin/users/abc/organization/1", "", 1, "admin@example.com", "admin")
	rr := httptest.NewRecorder()

	handler.RemoveUserFromOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusBadRequest)
	assertBodyContains(t, rr, "Invalid user ID")
}

func TestOrganizationHandler_GetUserOrganizations_InvalidUserID(t *testing.T) {
	handler := &OrganizationHandler{}

	req := createTestRequest(http.MethodGet, "/api/admin/users/abc/organizations", "")
	rr := httptest.NewRecorder()

	handler.GetUserOrganizations(rr, req)

	assertStatusCode(t, rr, http.StatusBadRequest)
	assertBodyContains(t, rr, "Invalid user ID")
}

func TestOrganizationHandler_GetOrganizationUsers_InvalidOrgID(t *testing.T) {
	handler := &OrganizationHandler{}

	req := createTestRequest(http.MethodGet, "/api/admin/organizations/abc/users", "")
	rr := httptest.NewRecorder()

	handler.GetOrganizationUsers(rr, req)

	assertStatusCode(t, rr, http.StatusBadRequest)
	assertBodyContains(t, rr, "Invalid organization ID")
}

// Test NewOrganizationHandler

func TestNewOrganizationHandler(t *testing.T) {
	handler := NewOrganizationHandler(nil, nil)
	if handler == nil {
		t.Error("NewOrganizationHandler should return a non-nil handler")
	}
}

// Test UpdateOrganization with valid JSON

func TestOrganizationHandler_UpdateOrganization_ValidIDInvalidJSON(t *testing.T) {
	handler := &OrganizationHandler{}

	req := createAuthenticatedRequest(http.MethodPut, "/api/admin/organizations/1", "{bad json", 1, "admin@example.com", "admin")
	req = addChiURLParam(req, "id", "1")
	rr := httptest.NewRecorder()

	handler.UpdateOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusBadRequest)
	assertBodyContains(t, rr, "Invalid request body")
}

// Test AssignUserToOrganization with valid JSON

func TestOrganizationHandler_AssignUserToOrganization_ValidIDInvalidJSON(t *testing.T) {
	handler := &OrganizationHandler{}

	req := createAuthenticatedRequest(http.MethodPost, "/api/admin/users/1/organization", "{bad json", 1, "admin@example.com", "admin")
	req = addChiURLParam(req, "id", "1")
	rr := httptest.NewRecorder()

	handler.AssignUserToOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusBadRequest)
	assertBodyContains(t, rr, "Invalid request body")
}

func TestOrganizationHandler_AssignUserToOrganization_MissingOrgIDWithValidUserID(t *testing.T) {
	handler := &OrganizationHandler{}

	req := createAuthenticatedRequest(http.MethodPost, "/api/admin/users/1/organization", `{}`, 1, "admin@example.com", "admin")
	req = addChiURLParam(req, "id", "1")
	rr := httptest.NewRecorder()

	handler.AssignUserToOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusBadRequest)
	assertBodyContains(t, rr, "Organization ID is required")
}

// Test RemoveUserFromOrganization with valid IDs

func TestOrganizationHandler_RemoveUserFromOrganization_InvalidOrgID(t *testing.T) {
	handler := &OrganizationHandler{}

	req := createAuthenticatedRequest(http.MethodDelete, "/api/admin/users/1/organization/abc", "", 1, "admin@example.com", "admin")
	req = addChiURLParam(req, "id", "1")
	// org_id is empty without chi context -> Invalid organization ID
	rr := httptest.NewRecorder()

	handler.RemoveUserFromOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusBadRequest)
	assertBodyContains(t, rr, "Invalid organization ID")
}

// Removed 14 panic-expectation tests:
// - TestOrganizationHandler_ListOrganizations_NilService
// - TestOrganizationHandler_GetOrganization_ValidIDNilService
// - TestOrganizationHandler_UpdateOrganization_ValidInputNilService
// - TestOrganizationHandler_DeleteOrganization_ValidIDNilService
// - TestOrganizationHandler_GetUserOrganizations_ValidIDNilService
// - TestOrganizationHandler_GetOrganizationUsers_ValidIDNilService
// - TestOrganizationHandler_RemoveUserFromOrganization_ValidIDsNilService
// - TestOrganizationHandler_CreateOrganization_ValidInputNilService
// - TestOrganizationHandler_AssignUserToOrganization_ValidInputNilService
// - TestOrganizationHandler_ListOrganizations_WithPagination (4 subtests)
// - TestOrganizationHandler_GetOrganization_DifferentIDs (3 subtests)
// - TestOrganizationHandler_UpdateOrganization_WithDescription
// - TestOrganizationHandler_GetUserOrganizations_DifferentIDs (3 subtests)
// - TestOrganizationHandler_GetOrganizationUsers_DifferentIDs (3 subtests)
// These tests verified nil pointer panics, not business logic.

// MockUserRepositoryForOrg is a mock that returns nil,nil for not found (matching real repo behavior)
type MockUserRepositoryForOrg struct {
	*MockUserRepository
}

func NewMockUserRepositoryForOrg() *MockUserRepositoryForOrg {
	return &MockUserRepositoryForOrg{
		MockUserRepository: NewMockUserRepository(),
	}
}

// Override GetByID to return nil,nil for not found (matching real repository pattern)
func (m *MockUserRepositoryForOrg) GetByID(id int64) (*domain.User, error) {
	if m.shouldError {
		return nil, m.errorToReturn
	}
	for _, u := range m.users {
		if u.ID == id {
			return u, nil
		}
	}
	return nil, nil // Real repo returns nil,nil for not found
}

// Helper to create organization handler with proper service
func createOrgHandlerWithMocks() (*OrganizationHandler, *MockOrganizationRepository, *MockUserRepositoryForOrg) {
	mockOrgRepo := NewMockOrganizationRepository()
	mockUserRepo := NewMockUserRepositoryForOrg()
	mockAuditLogRepo := NewMockAuditLogRepository()

	orgService := service.NewOrganizationService(mockOrgRepo, mockUserRepo, mockAuditLogRepo)

	handler := &OrganizationHandler{
		orgService: orgService,
		logger:     createTestLogger(),
	}

	return handler, mockOrgRepo, mockUserRepo
}

// ============================================================================
// Tests for GetOrganization with service
// ============================================================================

func TestOrganizationHandler_GetOrganization_Success(t *testing.T) {
	handler, _, _ := createOrgHandlerWithMocks()

	req := createTestRequest(http.MethodGet, "/api/admin/organizations/1", "")
	req = addChiURLParam(req, "id", "1")
	rr := httptest.NewRecorder()

	handler.GetOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusOK)
	assertBodyContains(t, rr, "Test Org")
}

func TestOrganizationHandler_GetOrganization_NotFound(t *testing.T) {
	handler, _, _ := createOrgHandlerWithMocks()

	req := createTestRequest(http.MethodGet, "/api/admin/organizations/999", "")
	req = addChiURLParam(req, "id", "999")
	rr := httptest.NewRecorder()

	handler.GetOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusNotFound)
	assertBodyContains(t, rr, "Organization not found")
}

func TestOrganizationHandler_GetOrganization_RepoError(t *testing.T) {
	handler, mockOrgRepo, _ := createOrgHandlerWithMocks()
	mockOrgRepo.SetError(ErrMockInternalError)

	req := createTestRequest(http.MethodGet, "/api/admin/organizations/1", "")
	req = addChiURLParam(req, "id", "1")
	rr := httptest.NewRecorder()

	handler.GetOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusInternalServerError)
	assertBodyContains(t, rr, "Failed to get organization")
}

// ============================================================================
// Tests for ListOrganizations with service
// ============================================================================

func TestOrganizationHandler_ListOrganizations_Success(t *testing.T) {
	handler, _, _ := createOrgHandlerWithMocks()

	req := createTestRequest(http.MethodGet, "/api/admin/organizations", "")
	rr := httptest.NewRecorder()

	handler.ListOrganizations(rr, req)

	assertStatusCode(t, rr, http.StatusOK)
	assertBodyContains(t, rr, "organizations")
	assertBodyContains(t, rr, "total")
}

func TestOrganizationHandler_ListOrganizations_WithPaginationParams(t *testing.T) {
	handler, _, _ := createOrgHandlerWithMocks()

	req := createTestRequest(http.MethodGet, "/api/admin/organizations?limit=10&offset=5", "")
	rr := httptest.NewRecorder()

	handler.ListOrganizations(rr, req)

	assertStatusCode(t, rr, http.StatusOK)
	assertBodyContains(t, rr, `"limit":10`)
	assertBodyContains(t, rr, `"offset":5`)
}

func TestOrganizationHandler_ListOrganizations_InvalidLimitIgnored(t *testing.T) {
	handler, _, _ := createOrgHandlerWithMocks()

	req := createTestRequest(http.MethodGet, "/api/admin/organizations?limit=-5&offset=abc", "")
	rr := httptest.NewRecorder()

	handler.ListOrganizations(rr, req)

	assertStatusCode(t, rr, http.StatusOK)
	// Default values should be used
	assertBodyContains(t, rr, `"limit":50`)
	assertBodyContains(t, rr, `"offset":0`)
}

func TestOrganizationHandler_ListOrganizations_RepoError(t *testing.T) {
	handler, mockOrgRepo, _ := createOrgHandlerWithMocks()
	mockOrgRepo.SetError(ErrMockInternalError)

	req := createTestRequest(http.MethodGet, "/api/admin/organizations", "")
	rr := httptest.NewRecorder()

	handler.ListOrganizations(rr, req)

	assertStatusCode(t, rr, http.StatusInternalServerError)
	assertBodyContains(t, rr, "Failed to list organizations")
}

// ============================================================================
// Tests for CreateOrganization with service
// ============================================================================

func TestOrganizationHandler_CreateOrganization_Success(t *testing.T) {
	handler, _, _ := createOrgHandlerWithMocks()

	req := createAuthenticatedRequest(http.MethodPost, "/api/admin/organizations",
		`{"name": "New Org", "description": "A new organization"}`, 1, "admin@example.com", "admin")
	rr := httptest.NewRecorder()

	handler.CreateOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusCreated)
	assertBodyContains(t, rr, "New Org")
}

func TestOrganizationHandler_CreateOrganization_NameExists(t *testing.T) {
	handler, _, _ := createOrgHandlerWithMocks()

	// Try to create org with name that already exists
	req := createAuthenticatedRequest(http.MethodPost, "/api/admin/organizations",
		`{"name": "Test Org"}`, 1, "admin@example.com", "admin")
	rr := httptest.NewRecorder()

	handler.CreateOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusConflict)
	assertBodyContains(t, rr, "Organization name already exists")
}

func TestOrganizationHandler_CreateOrganization_RepoError(t *testing.T) {
	handler, mockOrgRepo, _ := createOrgHandlerWithMocks()
	mockOrgRepo.SetError(ErrMockInternalError)

	req := createAuthenticatedRequest(http.MethodPost, "/api/admin/organizations",
		`{"name": "New Org"}`, 1, "admin@example.com", "admin")
	rr := httptest.NewRecorder()

	handler.CreateOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusInternalServerError)
	assertBodyContains(t, rr, "Failed to create organization")
}

// ============================================================================
// Tests for UpdateOrganization with service
// ============================================================================

func TestOrganizationHandler_UpdateOrganization_Success(t *testing.T) {
	handler, _, _ := createOrgHandlerWithMocks()

	req := createAuthenticatedRequest(http.MethodPut, "/api/admin/organizations/1",
		`{"name": "Updated Org", "description": "Updated description"}`, 1, "admin@example.com", "admin")
	req = addChiURLParam(req, "id", "1")
	rr := httptest.NewRecorder()

	handler.UpdateOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusOK)
	assertBodyContains(t, rr, "Updated Org")
}

func TestOrganizationHandler_UpdateOrganization_NotFound(t *testing.T) {
	handler, _, _ := createOrgHandlerWithMocks()

	req := createAuthenticatedRequest(http.MethodPut, "/api/admin/organizations/999",
		`{"name": "Updated Org"}`, 1, "admin@example.com", "admin")
	req = addChiURLParam(req, "id", "999")
	rr := httptest.NewRecorder()

	handler.UpdateOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusNotFound)
	assertBodyContains(t, rr, "Organization not found")
}

func TestOrganizationHandler_UpdateOrganization_RepoError(t *testing.T) {
	handler, mockOrgRepo, _ := createOrgHandlerWithMocks()
	mockOrgRepo.SetError(ErrMockInternalError)

	req := createAuthenticatedRequest(http.MethodPut, "/api/admin/organizations/1",
		`{"name": "Updated Org"}`, 1, "admin@example.com", "admin")
	req = addChiURLParam(req, "id", "1")
	rr := httptest.NewRecorder()

	handler.UpdateOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusInternalServerError)
	assertBodyContains(t, rr, "Failed to update organization")
}

// ============================================================================
// Tests for DeleteOrganization with service
// ============================================================================

func TestOrganizationHandler_DeleteOrganization_Success(t *testing.T) {
	handler, _, _ := createOrgHandlerWithMocks()

	req := createAuthenticatedRequest(http.MethodDelete, "/api/admin/organizations/1",
		"", 1, "admin@example.com", "admin")
	req = addChiURLParam(req, "id", "1")
	rr := httptest.NewRecorder()

	handler.DeleteOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusOK)
	assertBodyContains(t, rr, "Organization deleted successfully")
}

func TestOrganizationHandler_DeleteOrganization_NotFound(t *testing.T) {
	handler, _, _ := createOrgHandlerWithMocks()

	req := createAuthenticatedRequest(http.MethodDelete, "/api/admin/organizations/999",
		"", 1, "admin@example.com", "admin")
	req = addChiURLParam(req, "id", "999")
	rr := httptest.NewRecorder()

	handler.DeleteOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusNotFound)
	assertBodyContains(t, rr, "Organization not found")
}

func TestOrganizationHandler_DeleteOrganization_RepoError(t *testing.T) {
	handler, mockOrgRepo, _ := createOrgHandlerWithMocks()
	mockOrgRepo.SetError(ErrMockInternalError)

	req := createAuthenticatedRequest(http.MethodDelete, "/api/admin/organizations/1",
		"", 1, "admin@example.com", "admin")
	req = addChiURLParam(req, "id", "1")
	rr := httptest.NewRecorder()

	handler.DeleteOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusInternalServerError)
	assertBodyContains(t, rr, "Failed to delete organization")
}

// ============================================================================
// Tests for AssignUserToOrganization with service
// ============================================================================

func TestOrganizationHandler_AssignUserToOrganization_Success(t *testing.T) {
	handler, mockOrgRepo, _ := createOrgHandlerWithMocks()
	// Need to make IsUserInOrganization return false for new assignment
	mockOrgRepo.organizations = append(mockOrgRepo.organizations, nil) // Simulate no membership

	req := createAuthenticatedRequest(http.MethodPost, "/api/admin/users/1/organization",
		`{"organization_id": 1}`, 1, "admin@example.com", "admin")
	req = addChiURLParam(req, "id", "1")
	rr := httptest.NewRecorder()

	handler.AssignUserToOrganization(rr, req)

	// User is already a member in mock, so this should fail
	assertStatusCode(t, rr, http.StatusInternalServerError)
}

func TestOrganizationHandler_AssignUserToOrganization_OrgNotFound(t *testing.T) {
	handler, _, _ := createOrgHandlerWithMocks()

	req := createAuthenticatedRequest(http.MethodPost, "/api/admin/users/1/organization",
		`{"organization_id": 999}`, 1, "admin@example.com", "admin")
	req = addChiURLParam(req, "id", "1")
	rr := httptest.NewRecorder()

	handler.AssignUserToOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusNotFound)
	assertBodyContains(t, rr, "Organization not found")
}

func TestOrganizationHandler_AssignUserToOrganization_UserNotFound(t *testing.T) {
	handler, _, _ := createOrgHandlerWithMocks()

	req := createAuthenticatedRequest(http.MethodPost, "/api/admin/users/999/organization",
		`{"organization_id": 1}`, 1, "admin@example.com", "admin")
	req = addChiURLParam(req, "id", "999")
	rr := httptest.NewRecorder()

	handler.AssignUserToOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusNotFound)
	assertBodyContains(t, rr, "User not found")
}

func TestOrganizationHandler_AssignUserToOrganization_RepoError(t *testing.T) {
	handler, mockOrgRepo, _ := createOrgHandlerWithMocks()
	mockOrgRepo.SetError(ErrMockInternalError)

	req := createAuthenticatedRequest(http.MethodPost, "/api/admin/users/1/organization",
		`{"organization_id": 1}`, 1, "admin@example.com", "admin")
	req = addChiURLParam(req, "id", "1")
	rr := httptest.NewRecorder()

	handler.AssignUserToOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusInternalServerError)
	assertBodyContains(t, rr, "Failed to assign user")
}

// ============================================================================
// Tests for RemoveUserFromOrganization with service
// ============================================================================

func TestOrganizationHandler_RemoveUserFromOrganization_Success(t *testing.T) {
	handler, _, _ := createOrgHandlerWithMocks()

	req := createAuthenticatedRequest(http.MethodDelete, "/api/admin/users/1/organization/1",
		"", 1, "admin@example.com", "admin")
	req = addChiURLParams(req, map[string]string{"id": "1", "org_id": "1"})
	rr := httptest.NewRecorder()

	handler.RemoveUserFromOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusOK)
	assertBodyContains(t, rr, "User removed from organization successfully")
}

func TestOrganizationHandler_RemoveUserFromOrganization_UserNotFound(t *testing.T) {
	handler, _, _ := createOrgHandlerWithMocks()

	req := createAuthenticatedRequest(http.MethodDelete, "/api/admin/users/999/organization/1",
		"", 1, "admin@example.com", "admin")
	req = addChiURLParams(req, map[string]string{"id": "999", "org_id": "1"})
	rr := httptest.NewRecorder()

	handler.RemoveUserFromOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusNotFound)
	assertBodyContains(t, rr, "User not found")
}

func TestOrganizationHandler_RemoveUserFromOrganization_OrgNotFound(t *testing.T) {
	handler, _, _ := createOrgHandlerWithMocks()

	req := createAuthenticatedRequest(http.MethodDelete, "/api/admin/users/1/organization/999",
		"", 1, "admin@example.com", "admin")
	req = addChiURLParams(req, map[string]string{"id": "1", "org_id": "999"})
	rr := httptest.NewRecorder()

	handler.RemoveUserFromOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusNotFound)
	assertBodyContains(t, rr, "Organization not found")
}

func TestOrganizationHandler_RemoveUserFromOrganization_RepoError(t *testing.T) {
	handler, mockOrgRepo, _ := createOrgHandlerWithMocks()
	mockOrgRepo.SetError(ErrMockInternalError)

	req := createAuthenticatedRequest(http.MethodDelete, "/api/admin/users/1/organization/1",
		"", 1, "admin@example.com", "admin")
	req = addChiURLParams(req, map[string]string{"id": "1", "org_id": "1"})
	rr := httptest.NewRecorder()

	handler.RemoveUserFromOrganization(rr, req)

	assertStatusCode(t, rr, http.StatusInternalServerError)
	assertBodyContains(t, rr, "Failed to remove user")
}

// ============================================================================
// Tests for GetUserOrganizations with service
// ============================================================================

func TestOrganizationHandler_GetUserOrganizations_Success(t *testing.T) {
	handler, _, _ := createOrgHandlerWithMocks()

	req := createTestRequest(http.MethodGet, "/api/admin/users/1/organizations", "")
	req = addChiURLParam(req, "id", "1")
	rr := httptest.NewRecorder()

	handler.GetUserOrganizations(rr, req)

	assertStatusCode(t, rr, http.StatusOK)
	assertBodyContains(t, rr, "organizations")
	assertBodyContains(t, rr, "count")
}

func TestOrganizationHandler_GetUserOrganizations_UserNotFound(t *testing.T) {
	handler, _, _ := createOrgHandlerWithMocks()

	req := createTestRequest(http.MethodGet, "/api/admin/users/999/organizations", "")
	req = addChiURLParam(req, "id", "999")
	rr := httptest.NewRecorder()

	handler.GetUserOrganizations(rr, req)

	assertStatusCode(t, rr, http.StatusNotFound)
	assertBodyContains(t, rr, "User not found")
}

func TestOrganizationHandler_GetUserOrganizations_RepoError(t *testing.T) {
	handler, _, mockUserRepo := createOrgHandlerWithMocks()
	mockUserRepo.SetError(ErrMockInternalError)

	req := createTestRequest(http.MethodGet, "/api/admin/users/1/organizations", "")
	req = addChiURLParam(req, "id", "1")
	rr := httptest.NewRecorder()

	handler.GetUserOrganizations(rr, req)

	assertStatusCode(t, rr, http.StatusInternalServerError)
	assertBodyContains(t, rr, "Failed to get organizations")
}

// ============================================================================
// Tests for GetOrganizationUsers with service
// ============================================================================

func TestOrganizationHandler_GetOrganizationUsers_Success(t *testing.T) {
	handler, _, _ := createOrgHandlerWithMocks()

	req := createTestRequest(http.MethodGet, "/api/admin/organizations/1/users", "")
	req = addChiURLParam(req, "id", "1")
	rr := httptest.NewRecorder()

	handler.GetOrganizationUsers(rr, req)

	assertStatusCode(t, rr, http.StatusOK)
	assertBodyContains(t, rr, "users")
	assertBodyContains(t, rr, "count")
}

func TestOrganizationHandler_GetOrganizationUsers_OrgNotFound(t *testing.T) {
	handler, _, _ := createOrgHandlerWithMocks()

	req := createTestRequest(http.MethodGet, "/api/admin/organizations/999/users", "")
	req = addChiURLParam(req, "id", "999")
	rr := httptest.NewRecorder()

	handler.GetOrganizationUsers(rr, req)

	assertStatusCode(t, rr, http.StatusNotFound)
	assertBodyContains(t, rr, "Organization not found")
}

func TestOrganizationHandler_GetOrganizationUsers_RepoError(t *testing.T) {
	handler, mockOrgRepo, _ := createOrgHandlerWithMocks()
	mockOrgRepo.SetError(ErrMockInternalError)

	req := createTestRequest(http.MethodGet, "/api/admin/organizations/1/users", "")
	req = addChiURLParam(req, "id", "1")
	rr := httptest.NewRecorder()

	handler.GetOrganizationUsers(rr, req)

	assertStatusCode(t, rr, http.StatusInternalServerError)
	assertBodyContains(t, rr, "Failed to get users")
}