Public/TestCaseManagement/New-TcmTestCase.ps1

function New-TcmTestCase {
    <#
        .SYNOPSIS
            Creates a new test case YAML file with the specified properties.

        .DESCRIPTION
            Creates a new test case in YAML format that can be synchronized with Azure DevOps.
            The test case is saved to a file in a folder structure based on the area path.
            If no configuration exists, you must first create a .tcm-config.yaml file.

            The function validates input parameters, creates the necessary directory structure,
            and generates a properly formatted YAML file containing all test case metadata.

        .PARAMETER Id
            The unique identifier for the test case (e.g., "TC001").
            This will be used as the filename and must contain only letters, numbers, underscores, and hyphens.
            This parameter is mandatory.

        .PARAMETER Title
            The title of the test case. This parameter is mandatory.

        .PARAMETER Description
            A description of what the test case validates. Defaults to empty string if not specified.

        .PARAMETER AreaPath
            The area path for the test case in Azure DevOps (e.g., "Project\Area\Component").
            If not specified, uses the default from the configuration file.

        .PARAMETER IterationPath
            The iteration path for the test case in Azure DevOps.
            If not specified, uses the default from the configuration file.

        .PARAMETER Priority
            The priority of the test case (1-4, where 1 is highest priority).
            If not specified, uses the default from the configuration file.
            Valid values are 1, 2, 3, or 4.

        .PARAMETER Tags
            An array of tags to associate with the test case.
            Tags help categorize and filter test cases in Azure DevOps.

        .PARAMETER Steps
            An array of test steps. Each step should be a hashtable with 'action' and 'expectedResult' properties.
            Additional properties like 'attachments' are supported but not required.
            If not specified, a default empty step will be created.

        .PARAMETER OutputPath
            The relative path where the test case file should be created.
            If not specified, the file is created in a folder structure based on the area path.
            Should include the filename with .yaml extension.

        .PARAMETER TestCasesRoot
            The root directory for test cases.
            If not specified, uses the current directory or the directory containing the .tcm-config.yaml file.

        .PARAMETER Force
            Overwrites the test case file if it already exists without prompting.

        .EXAMPLE
            PS C:\> New-TcmTestCase -Id "TC001" -Title "Login Test" -Description "Test user login functionality"

            Creates a basic test case with default values for area path, iteration path, and priority.

        .EXAMPLE
            PS C:\> $steps = @(
                @{ action = "Navigate to login page"; expectedResult = "Login form displayed" },
                @{ action = "Enter valid credentials"; expectedResult = "User logged in successfully" },
                @{ action = "Click logout button"; expectedResult = "User logged out" }
            )
            PS C:\> New-TcmTestCase -Id "TC002" -Title "Login Flow" -Steps $steps -AreaPath "MyProject\Authentication"

            Creates a test case with multiple test steps and a specific area path.

        .EXAMPLE
            PS C:\> New-TcmTestCase -Id "TC003" -Title "API Test" -OutputPath "api/TC003-api-test.yaml" -TestCasesRoot "C:\MyTestCases" -Force

            Creates a test case in a specific file path within a custom test cases root directory,
            overwriting any existing file.

        .INPUTS
            None. This function does not accept pipeline input.

        .OUTPUTS
            System.Collections.Hashtable
            Returns a hashtable containing the created test case data with the following structure:
            - testCase: The test case metadata
            - metadata: File path and creation information

        .NOTES
            - Requires a .tcm-config.yaml file in the test cases root directory or a parent directory.
            - The test case ID must be unique within the test cases root.
            - Test steps are optional but recommended for comprehensive test cases.
            - The created YAML file can be synchronized with Azure DevOps using Sync-TcmTestCase.
            - Invalid characters in the ID will cause the function to throw an error.

        .LINK
            Sync-TcmTestCase

        .LINK
            New-TcmConfig

        .LINK
            Get-TcmTestCase
    #>


    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string] $Id,

        [Parameter(Mandatory)]
        [string] $Title,

        [string] $Description = "",

        [string] $AreaPath,

        [string] $IterationPath,

        [int] $Priority,

        [string[]] $Tags = @(),

        [hashtable[]] $Steps = @(),

        [string] $OutputPath,

        [string] $TestCasesRoot,

        [switch] $Force
    )

    try {
        # Get configuration
        $config = Get-TcmTestCaseConfig -TestCasesRoot $TestCasesRoot

        # Validate inputs
        if ([string]::IsNullOrWhiteSpace($Id)) {
            throw "Test case ID is required and cannot be empty."
        }
        if ($Id -notmatch '^[A-Za-z0-9_-]+$') {
            throw "Test case ID '$Id' contains invalid characters. Only letters, numbers, underscores, and hyphens are allowed."
        }

        if ([string]::IsNullOrWhiteSpace($Title)) {
            throw "Test case title is required and cannot be empty."
        }

        if ($Priority -and ($Priority -lt 1 -or $Priority -gt 4)) {
            throw "Priority must be between 1 and 4. Provided value: $Priority"
        }

        if ($Steps -and $Steps.Count -gt 0) {
            foreach ($i in 0..($Steps.Count - 1)) {
                $step = $Steps[$i]
                if (-not $step.action -or [string]::IsNullOrWhiteSpace($step.action)) {
                    throw "Step $($i + 1) is missing the required 'action' property."
                }
                if (-not $step.expectedResult -or [string]::IsNullOrWhiteSpace($step.expectedResult)) {
                    throw "Step $($i + 1) is missing the required 'expectedResult' property."
                }
            }
        }

        # Use defaults from config if not specified
        if (-not $AreaPath) { $AreaPath = $config.testCase.defaultAreaPath }
        if (-not $IterationPath) { $IterationPath = $config.testCase.defaultIterationPath }
        if (-not $Priority) { $Priority = $config.testCase.defaultPriority }

        # Determine output path
        if (-not $OutputPath) {
            $sanitizedTitle = $Title -replace '[^\w\s-]', '' -replace '\s+', '-'
            $fileName = "$Id-$sanitizedTitle.yaml".ToLower()
            # Use just the filename - let Save-TcmTestCaseYaml handle folder structure
            $OutputPath = $fileName
        }

        $fullOutputPath = Join-Path $config.TestCasesRoot $OutputPath

        # Check if test case with same ID already exists
        $existingFiles = Get-ChildItem -Path $config.TestCasesRoot -Filter "*.yaml" -Recurse -File |
            Where-Object {
                try {
                    $fileData = Get-TcmTestCaseFromFile -FilePath $_.FullName
                    $fileData.testCase.id -eq $Id
                } catch {
                    $false
                }
            }

        if ($existingFiles -and -not $Force) {
            throw "A test case with ID '$Id' already exists at: $($existingFiles[0].FullName). To overwrite the existing test case, use the -Force parameter."
        }

        # Ensure output directory exists
        $outputDir = Split-Path -Parent $fullOutputPath
        if (-not (Test-Path $outputDir)) {
            New-Item -Path $outputDir -ItemType Directory -Force | Out-Null
        }

        # Create test case object
        $now = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
        $currentUser = [Environment]::UserName + "@" + [Environment]::UserDomainName

        # Process steps
        $processedSteps = @()
        for ($i = 0; $i -lt $Steps.Count; $i++) {
            $step = $Steps[$i]
            $processedSteps += @{
                stepNumber     = $i + 1
                action         = $step.action
                expectedResult = $step.expectedResult
                attachments    = @()
            }
        }

        $testCaseData = [ordered]@{
            testCase = [ordered]@{
                id               = $Id
                title            = $Title
                areaPath         = $AreaPath
                iterationPath    = $IterationPath
                state            = $config.testCase.defaultState
                priority         = $Priority
                assignedTo       = ""
                tags             = $Tags
                description      = $Description
                preconditions    = ""
                steps            = $processedSteps
                automationStatus = "Not Automated"
                customFields     = @{}
            }
            history  = [ordered]@{
                createdAt      = $now
                createdBy      = $currentUser
                lastModifiedAt = $now
                lastModifiedBy = $currentUser
            }
        }

        $actualFilePath = Save-TcmTestCaseYaml -FilePath $fullOutputPath -Data $testCaseData -TestCasesRoot $config.TestCasesRoot

        Write-Host "Test case '$Id' created successfully at: $actualFilePath" -ForegroundColor Green

        # Return the created test case
        return Get-TcmTestCaseFromFile -FilePath $actualFilePath -IncludeMetadata
    } catch {
        throw "Failed to create test case '$Id'. Error: $($_.Exception.Message)"
    }
}