issue-operations.integration.test.ts•11.2 kB
/**
 * Integration tests for Issue Operations (Create/Update)
 * Tests the complete flow from permission checking to actual issue operations
 */
import { describe, test, expect, beforeAll } from "bun:test";
import { JiraHttpClient } from "@features/jira/client/http";
import { ProjectPermissionRepositoryImpl } from "@features/jira/projects/repositories/project-permission.repository";
import { IssueRepositoryImpl } from "@features/jira/issues/repositories/issue.repository";
import { CreateIssueUseCaseImpl } from "@features/jira/issues/use-cases/create-issue.use-case";
import { UpdateIssueUseCaseImpl } from "@features/jira/issues/use-cases/update-issue.use-case";
import { ProjectValidatorImpl } from "@features/jira/projects/validators/project.validator";
import { IssueTransitionRepositoryImpl } from "@features/jira/issues/repositories/issue-transition.repository";
import { WorklogRepositoryImpl } from "@features/jira/issues/repositories/worklog.repository";
import { JiraConfigService } from "@features/jira/client/config";
import type { Issue } from "@features/jira/issues/models/issue.models";
import {
  hasJiraCredentials,
  getJiraCredentialsSkipReason,
} from "../utils/jira-credentials";
describe("Issue Operations Integration Tests", () => {
  let httpClient: JiraHttpClient;
  let permissionRepository: ProjectPermissionRepositoryImpl;
  let issueRepository: IssueRepositoryImpl;
  let createIssueUseCase: CreateIssueUseCaseImpl;
  let updateIssueUseCase: UpdateIssueUseCaseImpl;
  let createdIssueKey: string | null = null;
  // Test configuration - these should be set via environment variables
  const testConfig = {
    hostUrl: process.env.JIRA_HOST || "https://example.atlassian.net",
    username: process.env.JIRA_USERNAME || "test@example.com",
    apiToken: process.env.JIRA_API_TOKEN || "test-token",
  };
  beforeAll(() => {
    if (!hasJiraCredentials()) {
      console.log(
        "⚠️ Skipping integration tests - no JIRA credentials provided",
      );
      console.log(`   Reason: ${getJiraCredentialsSkipReason()}`);
      console.log(
        "   Set JIRA_HOST, JIRA_USERNAME, and JIRA_API_TOKEN to run integration tests",
      );
      return;
    }
    // Initialize dependencies only when credentials are available
    const configService = new JiraConfigService(testConfig);
    httpClient = new JiraHttpClient(configService);
    permissionRepository = new ProjectPermissionRepositoryImpl(httpClient);
    issueRepository = new IssueRepositoryImpl(httpClient);
    // Initialize use cases with all required dependencies
    const projectValidator = new ProjectValidatorImpl(httpClient);
    const transitionRepository = new IssueTransitionRepositoryImpl(httpClient);
    const worklogRepository = new WorklogRepositoryImpl(httpClient);
    createIssueUseCase = new CreateIssueUseCaseImpl(
      issueRepository,
      projectValidator,
      permissionRepository,
    );
    updateIssueUseCase = new UpdateIssueUseCaseImpl(
      issueRepository,
      transitionRepository,
      worklogRepository,
      permissionRepository,
    );
  });
  describe("Permission Validation Flow", () => {
    test("should validate CREATE_ISSUES permission before creating issue", async () => {
      if (!hasJiraCredentials()) {
        console.log("⚠️ Skipping test - no JIRA credentials provided");
        return;
      }
      console.log("🔍 Testing CREATE_ISSUES permission validation...");
      try {
        const hasPermission =
          await permissionRepository.hasCreateIssuePermission("SEC");
        console.log(
          `CREATE_ISSUES permission: ${hasPermission ? "✅ GRANTED" : "❌ DENIED"}`,
        );
        expect(typeof hasPermission).toBe("boolean");
        if (hasPermission) {
          console.log(
            "✅ Permission validation working correctly for CREATE_ISSUES",
          );
        } else {
          console.warn(
            "⚠️ CREATE_ISSUES permission denied - check if this is expected",
          );
        }
      } catch (error) {
        console.error("❌ Permission validation failed:", error);
        throw error;
      }
    });
    test("should validate EDIT_ISSUES permission before updating issue", async () => {
      if (!hasJiraCredentials()) {
        console.log("⚠️ Skipping test - no JIRA credentials provided");
        return;
      }
      console.log("🔍 Testing EDIT_ISSUES permission validation...");
      try {
        const hasPermission =
          await permissionRepository.hasEditIssuePermission("SEC");
        console.log(
          `EDIT_ISSUES permission: ${hasPermission ? "✅ GRANTED" : "❌ DENIED"}`,
        );
        expect(typeof hasPermission).toBe("boolean");
        if (hasPermission) {
          console.log(
            "✅ Permission validation working correctly for EDIT_ISSUES",
          );
        } else {
          console.warn(
            "⚠️ EDIT_ISSUES permission denied - check if this is expected",
          );
        }
      } catch (error) {
        console.error("❌ Permission validation failed:", error);
        throw error;
      }
    });
  });
  describe("Issue Creation Flow", () => {
    test("should create issue in SEC project without permission errors", async () => {
      if (!hasJiraCredentials()) {
        console.log("⚠️ Skipping test - no JIRA credentials provided");
        return;
      }
      await testIssueCreation();
    });
  });
  async function testIssueCreation(): Promise<void> {
    console.log("🔍 Testing issue creation in SEC project...");
    try {
      const createRequest = {
        projectKey: "SEC",
        summary: `Integration Test Issue - ${new Date().toISOString()}`,
        description: "This is a test issue created by integration tests",
        issueType: "Task",
      };
      const createdIssue = await createIssueUseCase.execute(createRequest);
      validateCreatedIssue(createdIssue);
      logCreationSuccess(createdIssue);
    } catch (error) {
      handleCreationError(error);
      throw error;
    }
  }
  function validateCreatedIssue(createdIssue: Issue): void {
    expect(createdIssue).toBeDefined();
    expect(createdIssue.key).toBeDefined();
    expect(createdIssue.key).toMatch(/^SEC-\d+$/);
    expect(createdIssue.fields?.project?.key).toBe("SEC");
    expect(createdIssue.fields?.summary).toContain("Integration Test Issue");
    // Store for cleanup
    createdIssueKey = createdIssue.key;
  }
  function logCreationSuccess(createdIssue: Issue): void {
    console.log(`✅ Successfully created issue: ${createdIssue.key}`);
    console.log("📋 Issue details:", {
      key: createdIssue.key,
      summary: createdIssue.fields?.summary,
      project: createdIssue.fields?.project?.key,
      status: createdIssue.fields?.status?.name,
    });
  }
  function handleCreationError(error: unknown): void {
    console.error("❌ Issue creation failed:", error);
    if (error instanceof Error) {
      if (error.message.includes("permission")) {
        console.error(
          "🚨 PERMISSION ERROR: This suggests the fix didn't work!",
        );
      } else if (error.message.includes("404")) {
        console.error("🚨 PROJECT NOT FOUND: SEC project may not exist");
      }
    }
  }
  describe("Issue Update Flow", () => {
    test("should update created issue without permission errors", async () => {
      if (!hasJiraCredentials()) {
        console.log("⚠️ Skipping test - no JIRA credentials provided");
        return;
      }
      await testIssueUpdate();
    });
  });
  async function testIssueUpdate(): Promise<void> {
    if (!createdIssueKey) {
      console.log("⚠️ Skipping update test - no issue was created");
      return;
    }
    console.log(`🔍 Testing issue update for ${createdIssueKey}...`);
    try {
      const updateRequest = {
        issueKey: createdIssueKey,
        fields: {
          summary: `Updated Integration Test Issue - ${new Date().toISOString()}`,
          description: "This issue has been updated by integration tests",
        },
      };
      const updatedIssue = await updateIssueUseCase.execute(updateRequest);
      validateUpdatedIssue(updatedIssue);
      logUpdateSuccess(updatedIssue);
    } catch (error) {
      handleUpdateError(error);
      throw error;
    }
  }
  function validateUpdatedIssue(updatedIssue: Issue): void {
    expect(updatedIssue).toBeDefined();
    expect(createdIssueKey).toBeDefined();
    expect(updatedIssue.key).toBe(createdIssueKey as string);
    expect(updatedIssue.fields?.summary).toContain(
      "Updated Integration Test Issue",
    );
  }
  function logUpdateSuccess(updatedIssue: Issue): void {
    console.log(`✅ Successfully updated issue: ${updatedIssue.key}`);
    console.log("📋 Updated issue details:", {
      key: updatedIssue.key,
      summary: updatedIssue.fields?.summary,
      project: updatedIssue.fields?.project?.key,
      status: updatedIssue.fields?.status?.name,
    });
  }
  function handleUpdateError(error: unknown): void {
    console.error("❌ Issue update failed:", error);
    if (error instanceof Error) {
      if (error.message.includes("permission")) {
        console.error(
          "🚨 PERMISSION ERROR: This suggests the fix didn't work!",
        );
      } else if (error.message.includes("404")) {
        console.error("🚨 ISSUE NOT FOUND: Issue may have been deleted");
      }
    }
  }
  describe("Error Handling", () => {
    test("should handle non-existent project gracefully", async () => {
      if (!hasJiraCredentials()) {
        console.log("⚠️ Skipping test - no JIRA credentials provided");
        return;
      }
      console.log("🔍 Testing error handling for non-existent project...");
      try {
        const createRequest = {
          projectKey: "NONEXISTENT",
          summary: "Test issue for non-existent project",
          description: "This should fail",
          issueType: "Task",
        };
        await createIssueUseCase.execute(createRequest);
        // If we get here, something is wrong
        throw new Error(
          "Expected error for non-existent project, but operation succeeded",
        );
      } catch (error) {
        console.log("✅ Correctly handled non-existent project error");
        expect(error).toBeDefined();
        if (error instanceof Error) {
          console.log(`Error message: ${error.message}`);
          expect(error.message).toMatch(/project|not found|permission/i);
        }
      }
    });
  });
});
/**
 * Test runner instructions:
 *
 * To run these integration tests with real JIRA credentials:
 *
 * 1. Set environment variables:
 *    export JIRA_HOST="https://your-domain.atlassian.net"
 *    export JIRA_USERNAME="your-email@domain.com"
 *    export JIRA_API_TOKEN="your-api-token"
 *
 * 2. Run integration tests:
 *    bun run test:integration
 *
 * Without credentials, these tests will be automatically skipped.
 *
 * Note: These tests will create and update real issues in your JIRA instance.
 * Make sure you have appropriate permissions and are testing in a safe environment.
 */