worklog-api.integration.test.ts•9.22 kB
/**
 * Worklog API Integration Tests
 *
 * Tests complete worklog workflows from handlers through use cases to repositories
 */
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { AddWorklogHandler } from "@features/jira/issues/handlers/add-worklog.handler";
import { DeleteWorklogHandler } from "@features/jira/issues/handlers/delete-worklog.handler";
import { GetWorklogsHandler } from "@features/jira/issues/handlers/get-worklogs.handler";
import { UpdateWorklogHandler } from "@features/jira/issues/handlers/update-worklog.handler";
import {
  AddWorklogUseCaseImpl,
  DeleteWorklogUseCaseImpl,
  GetWorklogsUseCaseImpl,
  UpdateWorklogUseCaseImpl,
} from "@features/jira/issues/use-cases/worklog.use-cases";
import { WorklogValidatorImpl } from "@features/jira/issues/validators/worklog.validator";
import {
  mockWorklogEntry,
  worklogMockFactory,
} from "@test/mocks/worklog/worklog-entry.mock";
import { jiraApiMocks } from "@test/utils/mock-helpers";
describe("Worklog API Integration", () => {
  let addHandler: AddWorklogHandler;
  let getHandler: GetWorklogsHandler;
  let updateHandler: UpdateWorklogHandler;
  let deleteHandler: DeleteWorklogHandler;
  let validator: WorklogValidatorImpl;
  beforeEach(() => {
    // Create mock repository
    const mockRepository = {
      addWorklog: async () => mockWorklogEntry,
      getWorklogs: async () => [mockWorklogEntry],
      updateWorklog: async () => mockWorklogEntry,
      deleteWorklog: async () => undefined,
    };
    // Create use cases with mock repository
    const addUseCase = new AddWorklogUseCaseImpl(mockRepository);
    const getUseCase = new GetWorklogsUseCaseImpl(mockRepository);
    const updateUseCase = new UpdateWorklogUseCaseImpl(mockRepository);
    const deleteUseCase = new DeleteWorklogUseCaseImpl(mockRepository);
    // Create validator
    validator = new WorklogValidatorImpl();
    // Create handlers
    addHandler = new AddWorklogHandler(addUseCase, validator);
    getHandler = new GetWorklogsHandler(getUseCase, validator);
    updateHandler = new UpdateWorklogHandler(updateUseCase, validator);
    deleteHandler = new DeleteWorklogHandler(deleteUseCase, validator);
  });
  afterEach(() => {
    jiraApiMocks.clearMocks();
  });
  describe("Complete CRUD Workflow", () => {
    test("should complete full worklog lifecycle", async () => {
      const issueKey = "TEST-123";
      // 1. Add worklog
      const addParams = {
        issueKey,
        timeSpent: "2h",
        comment: "Initial work on feature",
      };
      const addResult = await addHandler.handle(addParams);
      expect(addResult.success).toBe(true);
      expect(addResult.data).toBeDefined();
      // 2. Get worklogs
      const getParams = { issueKey };
      const getResult = await getHandler.handle(getParams);
      expect(getResult.success).toBe(true);
      expect(getResult.data).toBeDefined();
      // 3. Update worklog
      const updateParams = {
        issueKey,
        worklogId: "10001",
        timeSpent: "3h",
        comment: "Updated work description",
      };
      const updateResult = await updateHandler.handle(updateParams);
      expect(updateResult.success).toBe(true);
      expect(updateResult.data).toBeDefined();
      // 4. Delete worklog
      const deleteParams = {
        issueKey,
        worklogId: "10001",
      };
      const deleteResult = await deleteHandler.handle(deleteParams);
      expect(deleteResult.success).toBe(true);
      expect(deleteResult.data).toBeDefined();
    });
  });
  describe("Cross-Handler Data Consistency", () => {
    test("should maintain data consistency across operations", async () => {
      const issueKey = "TEST-456";
      const worklogData = worklogMockFactory.createWorklogEntry({
        issueId: "10456",
        timeSpent: "4h",
        timeSpentSeconds: 14400,
      });
      // Mock repository to return consistent data
      const mockRepository = {
        addWorklog: async () => worklogData,
        getWorklogs: async () => [worklogData],
        updateWorklog: async () => ({
          ...worklogData,
          timeSpent: "5h",
          timeSpentSeconds: 18000,
        }),
        deleteWorklog: async () => undefined,
      };
      const addUseCase = new AddWorklogUseCaseImpl(mockRepository);
      const getUseCase = new GetWorklogsUseCaseImpl(mockRepository);
      const updateUseCase = new UpdateWorklogUseCaseImpl(mockRepository);
      const addHandler = new AddWorklogHandler(addUseCase, validator);
      const getHandler = new GetWorklogsHandler(getUseCase, validator);
      const updateHandler = new UpdateWorklogHandler(updateUseCase, validator);
      // Add worklog
      const addResult = await addHandler.handle({
        issueKey,
        timeSpent: "4h",
        comment: "Consistent data test",
      });
      expect(addResult.success).toBe(true);
      // Get worklogs should return the same data
      const getResult = await getHandler.handle({ issueKey });
      expect(getResult.success).toBe(true);
      // Update should maintain consistency
      const updateResult = await updateHandler.handle({
        issueKey,
        worklogId: worklogData.id,
        timeSpent: "5h",
      });
      expect(updateResult.success).toBe(true);
    });
  });
  describe("Error Propagation", () => {
    test("should properly propagate repository errors through layers", async () => {
      const errorRepository = {
        addWorklog: async () => {
          throw new Error("Database connection failed");
        },
        getWorklogs: async () => {
          throw new Error("Database connection failed");
        },
        updateWorklog: async () => {
          throw new Error("Database connection failed");
        },
        deleteWorklog: async () => {
          throw new Error("Database connection failed");
        },
      };
      const addUseCase = new AddWorklogUseCaseImpl(errorRepository);
      const getUseCase = new GetWorklogsUseCaseImpl(errorRepository);
      const updateUseCase = new UpdateWorklogUseCaseImpl(errorRepository);
      const deleteUseCase = new DeleteWorklogUseCaseImpl(errorRepository);
      const addHandler = new AddWorklogHandler(addUseCase, validator);
      const getHandler = new GetWorklogsHandler(getUseCase, validator);
      const updateHandler = new UpdateWorklogHandler(updateUseCase, validator);
      const deleteHandler = new DeleteWorklogHandler(deleteUseCase, validator);
      // Test error propagation for each operation
      const addResult = await addHandler.handle({
        issueKey: "TEST-123",
        timeSpent: "2h",
      });
      expect(addResult.success).toBe(false);
      expect(addResult.error).toContain("Database connection failed");
      const getResult = await getHandler.handle({
        issueKey: "TEST-123",
      });
      expect(getResult.success).toBe(false);
      expect(getResult.error).toContain("Database connection failed");
      const updateResult = await updateHandler.handle({
        issueKey: "TEST-123",
        worklogId: "10001",
        timeSpent: "3h",
      });
      expect(updateResult.success).toBe(false);
      expect(updateResult.error).toContain("Database connection failed");
      const deleteResult = await deleteHandler.handle({
        issueKey: "TEST-123",
        worklogId: "10001",
      });
      expect(deleteResult.success).toBe(false);
      expect(deleteResult.error).toContain("Database connection failed");
    });
  });
  describe("Validation Integration", () => {
    test("should validate parameters at handler level", async () => {
      // Test invalid time format
      const invalidTimeResult = await addHandler.handle({
        issueKey: "TEST-123",
        timeSpent: "invalid-time",
      });
      expect(invalidTimeResult.success).toBe(false);
      // Test invalid issue key
      const invalidIssueResult = await getHandler.handle({
        issueKey: "",
      });
      expect(invalidIssueResult.success).toBe(false);
      // Test missing required fields
      const missingFieldsResult = await updateHandler.handle({
        issueKey: "TEST-123",
        // Missing worklogId
      });
      expect(missingFieldsResult.success).toBe(false);
    });
  });
  describe("Performance Integration", () => {
    test("should handle large worklog operations efficiently", async () => {
      const largeWorklogList = worklogMockFactory.createWorklogList(100);
      const mockRepository = {
        addWorklog: async () => mockWorklogEntry,
        getWorklogs: async () => largeWorklogList,
        updateWorklog: async () => mockWorklogEntry,
        deleteWorklog: async () => undefined,
      };
      const getUseCase = new GetWorklogsUseCaseImpl(mockRepository);
      const getHandler = new GetWorklogsHandler(getUseCase, validator);
      const startTime = Date.now();
      const result = await getHandler.handle({
        issueKey: "TEST-123",
        maxResults: 1000,
      });
      const endTime = Date.now();
      const executionTime = endTime - startTime;
      expect(result.success).toBe(true);
      expect(executionTime).toBeLessThan(1000); // Should complete within 1 second
    });
  });
});