name: Test

on:
  push:
    branches:
      - main
    paths-ignore:
      - '**.md'
  pull_request:
    paths-ignore:
      - '**.md'

permissions:
  contents: read
  actions: write

jobs:
  build:
    name: Build

    strategy:
      matrix:
        runs-on: [ubuntu-latest, macos-latest, windows-latest]
      fail-fast: false

    runs-on: ${{ matrix.runs-on }}

    steps:
    - name: Checkout
      uses: actions/checkout@v4

    - name: Setup Node 24
      uses: actions/setup-node@v4
      with:
        node-version: 24.x
        cache: 'npm'

    - name: Install dependencies
      run: npm ci

    - name: Compile
      run: npm run build

    - name: Lint
      run: npm run lint

    - name: Format
      run: npm run format-check

    - name: Test
      run: npm run test

    # Test end-to-end by uploading a few artifacts and then downloading them
    - name: Create artifact files
      run: |
        mkdir -p path/to/dir-1
        mkdir -p path/to/dir-2
        mkdir -p path/to/dir-3
        mkdir -p symlink/
        echo "Lorem ipsum dolor sit amet" > path/to/dir-1/file1.txt
        echo "Hello world from file #2" > path/to/dir-2/file2.txt
        echo "Hello from a symlinked file" > symlink/original.txt
        ln -s $(pwd)/symlink/original.txt symlink/abs.txt
        ln -s original.txt symlink/rel.txt
      shell: bash

    # Upload a single file artifact
    - name: 'Upload artifact #1'
      uses: ./
      with:
        name: 'Artifact-A-${{ matrix.runs-on }}'
        path: path/to/dir-1/file1.txt

    # Upload using a wildcard pattern
    - name: 'Upload artifact #2'
      uses: ./
      with:
        name: 'Artifact-Wildcard-${{ matrix.runs-on }}'
        path: path/**/dir*/

    # Upload a multi-path artifact
    - name: 'Upload artifact #3'
      uses: ./
      with:
        name: 'Multi-Path-Artifact-${{ matrix.runs-on }}'
        path: |
          path/to/dir-1/*
          path/to/dir-[23]/*
          !path/to/dir-3/*.txt

    - name: 'Upload symlinked artifact'
      uses: ./
      with:
        name: 'Symlinked-Artifact-${{ matrix.runs-on }}'
        path: |
          symlink/abs.txt
          symlink/rel.txt

    # Download Artifact #1 and verify the correctness of the content
    - name: 'Download artifact #1'
      uses: actions/download-artifact@main
      with:
        name: 'Artifact-A-${{ matrix.runs-on }}'
        path: some/new/path

    - name: 'Verify Artifact #1'
      run: |
        $file = "some/new/path/file1.txt"
        if(!(Test-Path -path $file))
        {
            Write-Error "Expected file does not exist"
        }
        if(!((Get-Content $file) -ceq "Lorem ipsum dolor sit amet"))
        {
            Write-Error "File contents of downloaded artifact are incorrect"
        }
      shell: pwsh

    # Download Artifact #2 and verify the correctness of the content
    - name: 'Download artifact #2'
      uses: actions/download-artifact@main
      with:
        name: 'Artifact-Wildcard-${{ matrix.runs-on }}'
        path: some/other/path

    - name: 'Verify Artifact #2'
      run: |
        $file1 = "some/other/path/to/dir-1/file1.txt"
        $file2 = "some/other/path/to/dir-2/file2.txt"
        if(!(Test-Path -path $file1) -or !(Test-Path -path $file2))
        {
            Write-Error "Expected files do not exist"
        }
        if(!((Get-Content $file1) -ceq "Lorem ipsum dolor sit amet") -or !((Get-Content $file2) -ceq "Hello world from file #2"))
        {
            Write-Error "File contents of downloaded artifacts are incorrect"
        }
      shell: pwsh

    # Download Artifact #4 and verify the correctness of the content
    - name: 'Download artifact #4'
      uses: actions/download-artifact@main
      with:
        name: 'Multi-Path-Artifact-${{ matrix.runs-on }}'
        path: multi/artifact

    - name: 'Verify Artifact #4'
      run: |
        $file1 = "multi/artifact/dir-1/file1.txt"
        $file2 = "multi/artifact/dir-2/file2.txt"
        if(!(Test-Path -path $file1) -or !(Test-Path -path $file2))
        {
            Write-Error "Expected files do not exist"
        }
        if(!((Get-Content $file1) -ceq "Lorem ipsum dolor sit amet") -or !((Get-Content $file2) -ceq "Hello world from file #2"))
        {
            Write-Error "File contents of downloaded artifacts are incorrect"
        }
      shell: pwsh

    - name: 'Download symlinked artifact'
      uses: actions/download-artifact@main
      with:
        name: 'Symlinked-Artifact-${{ matrix.runs-on }}'
        path: from/symlink

    - name: 'Verify symlinked artifact'
      run: |
        $abs = "from/symlink/abs.txt"
        if(!(Test-Path -path $abs))
        {
            Write-Error "Expected file does not exist"
        }
        if(!((Get-Content $abs) -ceq "Hello from a symlinked file"))
        {
            Write-Error "File contents of downloaded artifact are incorrect"
        }
        $rel = "from/symlink/rel.txt"
        if(!(Test-Path -path $rel))
        {
            Write-Error "Expected file does not exist"
        }
        if(!((Get-Content $rel) -ceq "Hello from a symlinked file"))
        {
            Write-Error "File contents of downloaded artifact are incorrect"
        }
      shell: pwsh

    - name: 'Alter file 1 content'
      run: |
        echo "This file has changed" > path/to/dir-1/file1.txt

    # Replace the contents of Artifact #1
    - name: 'Overwrite artifact #1'
      uses: ./
      with:
        name: 'Artifact-A-${{ matrix.runs-on }}'
        path: path/to/dir-1/file1.txt
        overwrite: true

    # Download replaced Artifact #1 and verify the correctness of the content
    - name: 'Download artifact #1 again'
      uses: actions/download-artifact@main
      with:
        name: 'Artifact-A-${{ matrix.runs-on }}'
        path: overwrite/some/new/path

    - name: 'Verify Artifact #1 again'
      run: |
        $file = "overwrite/some/new/path/file1.txt"
        if(!(Test-Path -path $file))
        {
            Write-Error "Expected file does not exist"
        }
        if(!((Get-Content $file) -ceq "This file has changed"))
        {
            Write-Error "File contents of downloaded artifact are incorrect"
        }
      shell: pwsh

    # Upload a single file without archiving (direct file upload)
    - name: 'Create direct upload file'
      run: echo -n 'direct file upload content' > direct-upload-${{ matrix.runs-on }}.txt
      shell: bash

    - name: 'Upload direct file artifact'
      uses: ./
      with:
        name: 'Direct-File-${{ matrix.runs-on }}'
        path: direct-upload-${{ matrix.runs-on }}.txt
        archive: false

    - name: 'Download direct file artifact'
      uses: actions/download-artifact@main
      with:
        name: direct-upload-${{ matrix.runs-on }}.txt
        path: direct-download

    - name: 'Verify direct file artifact'
      run: |
        $file = "direct-download/direct-upload-${{ matrix.runs-on }}.txt"
        if(!(Test-Path -path $file))
        {
            Write-Error "Expected file does not exist"
        }
        if(!((Get-Content $file -Raw).TrimEnd() -ceq "direct file upload content"))
        {
            Write-Error "File contents of downloaded artifact are incorrect"
        }
      shell: pwsh

  upload-html-report:
    name: Upload HTML Report
    runs-on: ubuntu-latest

    steps:
    - name: Checkout
      uses: actions/checkout@v4

    - name: Setup Node 24
      uses: actions/setup-node@v4
      with:
        node-version: 24.x
        cache: 'npm'

    - name: Install dependencies
      run: npm ci

    - name: Compile
      run: npm run build

    - name: Create HTML report
      run: |
        cat > report.html << 'EOF'
        <!DOCTYPE html>
        <html lang="en">
        <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Artifact Upload Test Report</title>
          <style>
            body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif; max-width: 800px; margin: 40px auto; padding: 0 20px; color: #24292f; }
            h1 { border-bottom: 1px solid #d0d7de; padding-bottom: 8px; }
            .success { color: #1a7f37; }
            .info { background: #ddf4ff; border: 1px solid #54aeff; border-radius: 6px; padding: 12px 16px; margin: 16px 0; }
            table { border-collapse: collapse; width: 100%; margin: 16px 0; }
            th, td { border: 1px solid #d0d7de; padding: 8px 12px; text-align: left; }
            th { background: #f6f8fa; }
          </style>
        </head>
        <body>
          <h1>Artifact Upload Test Report</h1>
          <div class="info">
            <strong>This HTML file was uploaded as a single un-zipped artifact.</strong>
            If you can see this in the browser, the feature is working correctly!
          </div>
          <table>
            <tr><th>Property</th><th>Value</th></tr>
            <tr><td>Upload method</td><td><code>archive: false</code></td></tr>
            <tr><td>Content-Type</td><td><code>text/html</code></td></tr>
            <tr><td>File</td><td><code>report.html</code></td></tr>
          </table>
          <p class="success">&#10004; Single file upload is working!</p>
        </body>
        </html>
        EOF

    - name: Upload HTML report (no archive)
      uses: ./
      with:
        name: 'test-report'
        path: report.html
        archive: false

  merge:
    name: Merge
    needs: build
    runs-on: ubuntu-latest

    steps:
    - name: Checkout
      uses: actions/checkout@v4

    # Merge all artifacts from previous jobs
    - name: Merge all artifacts in run
      uses: ./merge/
      with:
        # our matrix produces artifacts with the same file, this prevents "stomping" on each other, also makes it
        # easier to identify each of the merged artifacts
        separate-directories: true
    - name: 'Download merged artifacts'
      uses: actions/download-artifact@main
      with:
        name: merged-artifacts
        path: all-merged-artifacts
    - name: 'Check merged artifact has directories for each artifact'
      run: |
        $artifacts = @(
          "Artifact-A-ubuntu-latest",
          "Artifact-A-macos-latest",
          "Artifact-A-windows-latest",
          "Artifact-Wildcard-ubuntu-latest",
          "Artifact-Wildcard-macos-latest",
          "Artifact-Wildcard-windows-latest",
          "Multi-Path-Artifact-ubuntu-latest",
          "Multi-Path-Artifact-macos-latest",
          "Multi-Path-Artifact-windows-latest"
        )

        foreach ($artifact in $artifacts) {
          $path = "all-merged-artifacts/$artifact"
          if (!(Test-Path $path)) {
            Write-Error "$path does not exist."
          }
        }
      shell: pwsh

    # Merge Artifact-A-* from previous jobs
    - name: Merge all Artifact-A
      uses: ./merge/
      with:
        name: Merged-Artifact-As
        pattern: 'Artifact-A-*'
        separate-directories: true

    # Download merged artifacts and verify the correctness of the content
    - name: 'Download merged artifacts'
      uses: actions/download-artifact@main
      with:
        name: Merged-Artifact-As
        path: merged-artifact-a

    - name: 'Verify merged artifacts'
      run: |
        $files = @(
          "merged-artifact-a/Artifact-A-ubuntu-latest/file1.txt",
          "merged-artifact-a/Artifact-A-macos-latest/file1.txt",
          "merged-artifact-a/Artifact-A-windows-latest/file1.txt"
        )

        foreach ($file in $files) {
          if (!(Test-Path $file)) {
            Write-Error "$file does not exist."
          }

          if (!((Get-Content $file) -ceq "This file has changed")) {
            Write-Error "$file has incorrect content."
          }
        }
      shell: pwsh

  cleanup:
    name: Cleanup Artifacts
    needs: [build, merge]
    runs-on: ubuntu-latest

    steps:
    - name: Delete test artifacts
      uses: actions/github-script@v8
      with:
        script: |
          const keep = ['report.html'];
          const owner = context.repo.owner;
          const repo = context.repo.repo;
          const runId = context.runId;

          const {data: {artifacts}} = await github.rest.actions.listWorkflowRunArtifacts({
            owner,
            repo,
            run_id: runId
          });

          for (const a of artifacts) {
            if (keep.includes(a.name)) {
              console.log(`Keeping artifact '${a.name}'`);
              continue;
            }
            try {
              await github.rest.actions.deleteArtifact({
                owner,
                repo,
                artifact_id: a.id
              });
              console.log(`Deleted artifact '${a.name}'`);
            } catch (err) {
              console.log(`Could not delete artifact '${a.name}': ${err.message}`);
            }
          }
