From 110db582bd18b0b88e75c6bc38fbe678b18ccfc1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 13 Jan 2026 23:24:02 +0000 Subject: [PATCH 1/2] Initial plan From 7d71e1ee644588aa12d2183e4268a9815a72a815 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 13 Jan 2026 23:33:37 +0000 Subject: [PATCH 2/2] Fix SFTP test configuration for chrooted environment - Change SFTP container to use command array format for proper parsing - Set SFTP_TEST_ROOT to "upload" directory which is writable by test user - Remove volume mount that was causing permission issues - The atmoz/sftp image chroots users and only specified directories are writable - This fixes "No such file" errors when SFTP tests try to create directories Co-authored-by: Menelion <595597+Menelion@users.noreply.github.com> --- .github/workflows/dotnet.yml | 380 +++++++++++++++++------------------ docker-compose.test.yml | 125 ++++++------ 2 files changed, 251 insertions(+), 254 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 3c26299..29d02df 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -1,190 +1,190 @@ -# This workflow will build a .NET project -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net - -name: .NET - -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v6 - - - name: Cleanup previous test services - run: | - echo "=== Cleaning up any previous test services ===" - docker compose -f docker-compose.test.yml down -v --remove-orphans || true - sudo rm -rf /tmp/localstack || true - echo "=== Removing stale Docker volumes ===" - docker volume rm sharp-sync_sftp-data || true - docker volume rm sharp-sync_ftp-data || true - docker volume rm sharp-sync_webdav-data || true - - - name: Start test services - run: | - echo "=== Starting test services with docker-compose ===" - docker compose -f docker-compose.test.yml up -d - - - name: Wait for services to be ready - run: | - echo "=== Waiting for services to be healthy ===" - docker compose -f docker-compose.test.yml ps - - echo "=== Waiting for SFTP server ===" - for i in {1..18}; do - nc -z localhost 2222 && echo "SFTP ready" && break - echo "SFTP not ready, retrying in 5 seconds... ($i/18)" - sleep 5 - done - - echo "=== Waiting for FTP server ===" - for i in {1..18}; do - nc -z localhost 21 && echo "FTP ready" && break - echo "FTP not ready, retrying in 5 seconds... ($i/18)" - sleep 5 - done - - echo "=== Waiting for LocalStack ===" - for i in {1..30}; do - curl -sf http://localhost:4566/_localstack/health && echo "LocalStack ready" && break - echo "LocalStack not ready, retrying in 10 seconds... ($i/30)" - sleep 10 - done - - echo "=== Waiting for WebDAV server ===" - WEBDAV_BASIC_READY=false - for i in {1..30}; do - if curl -sf -u testuser:testpass http://localhost:8080/ > /dev/null 2>&1; then - echo "WebDAV responding to basic requests" - WEBDAV_BASIC_READY=true - break - fi - echo "WebDAV not responding, retrying in 5 seconds... ($i/30)" - sleep 5 - done - - if [ "$WEBDAV_BASIC_READY" = "false" ]; then - echo "ERROR: WebDAV server not responding after 30 attempts" - docker compose -f docker-compose.test.yml logs webdav - exit 1 - fi - - echo "=== Checking WebDAV full functionality (MKCOL) ===" - WEBDAV_FULLY_READY=false - for i in {1..40}; do - # Show verbose output for debugging - echo "Attempt $i: Testing MKCOL operation..." - MKCOL_RESULT=$(curl -s -w "%{http_code}" -u testuser:testpass -X MKCOL http://localhost:8080/_health-check-dir/ -o /dev/null 2>&1) - echo "MKCOL response code: $MKCOL_RESULT" - - if [ "$MKCOL_RESULT" = "201" ] || [ "$MKCOL_RESULT" = "405" ]; then - echo "WebDAV is fully operational (MKCOL working)" - # Cleanup test directory - curl -sf -u testuser:testpass -X DELETE http://localhost:8080/_health-check-dir/ > /dev/null 2>&1 || true - WEBDAV_FULLY_READY=true - break - fi - - # Show container status and logs on failures - if [ $((i % 5)) -eq 0 ]; then - echo "--- WebDAV container status ---" - docker compose -f docker-compose.test.yml ps webdav - echo "--- Recent WebDAV logs ---" - docker compose -f docker-compose.test.yml logs --tail=10 webdav - fi - - echo "MKCOL not working yet, retrying in 5 seconds..." - sleep 5 - done - - if [ "$WEBDAV_FULLY_READY" = "false" ]; then - echo "ERROR: WebDAV MKCOL not working after 40 attempts" - echo "=== Full WebDAV logs ===" - docker compose -f docker-compose.test.yml logs webdav - exit 1 - fi - - echo "=== Final service status ===" - docker compose -f docker-compose.test.yml ps - - - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - dotnet-version: 8.0.x - - name: Restore dependencies - run: dotnet restore - - name: Check format - run: dotnet format --verify-no-changes - - name: Build - run: dotnet build --no-restore - - name: Create S3 test bucket - run: | - docker exec sharp-sync-localstack-1 awslocal s3 mb s3://test-bucket - - - name: Debug WebDAV setup - run: | - echo "=== WebDAV Container Status ===" - docker compose -f docker-compose.test.yml ps webdav - echo "" - echo "=== WebDAV Container Logs ===" - docker compose -f docker-compose.test.yml logs webdav - echo "" - echo "=== Testing WebDAV Operations ===" - echo "PROPFIND (list root):" - curl -s -w "\nHTTP Status: %{http_code}\n" -u testuser:testpass -X PROPFIND http://localhost:8080/ -H "Depth: 1" | head -30 - echo "" - echo "PUT (write test):" - echo "test content" | curl -s -w "\nHTTP Status: %{http_code}\n" -u testuser:testpass -X PUT http://localhost:8080/_debug-test.txt -d @- - echo "" - echo "DELETE (cleanup):" - curl -s -w "\nHTTP Status: %{http_code}\n" -u testuser:testpass -X DELETE http://localhost:8080/_debug-test.txt - - - name: Prepare WebDAV test root - run: | - echo "=== Creating WebDAV test root directory ===" - # Delete existing test root if present - curl -sf -u testuser:testpass -X DELETE http://localhost:8080/ci-root/ --output /dev/null 2>&1 || true - # Create fresh test root - curl -sf -u testuser:testpass -X MKCOL http://localhost:8080/ci-root/ - echo "WebDAV test root created successfully" - - - name: Test - run: dotnet test --no-build --verbosity normal - env: - SFTP_TEST_HOST: localhost - SFTP_TEST_PORT: 2222 - SFTP_TEST_USER: testuser - SFTP_TEST_PASS: testpass - SFTP_TEST_ROOT: "" - FTP_TEST_HOST: localhost - FTP_TEST_PORT: 21 - FTP_TEST_USER: testuser - FTP_TEST_PASS: testpass - FTP_TEST_ROOT: "" - S3_TEST_BUCKET: test-bucket - S3_TEST_ACCESS_KEY: test - S3_TEST_SECRET_KEY: test - S3_TEST_ENDPOINT: http://localhost:4566 - S3_TEST_PREFIX: sharpsync-tests - WEBDAV_TEST_URL: http://localhost:8080/ - WEBDAV_TEST_USER: testuser - WEBDAV_TEST_PASS: testpass - WEBDAV_TEST_ROOT: "ci-root" - - - name: Dump container logs - if: failure() - run: | - echo "=== Container logs for debugging ===" - docker compose -f docker-compose.test.yml logs - - - name: Stop test services - if: always() - run: | - docker compose -f docker-compose.test.yml down -v --remove-orphans || true +# This workflow will build a .NET project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net + +name: .NET + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Cleanup previous test services + run: | + echo "=== Cleaning up any previous test services ===" + docker compose -f docker-compose.test.yml down -v --remove-orphans || true + sudo rm -rf /tmp/localstack || true + echo "=== Removing stale Docker volumes ===" + docker volume rm sharp-sync_sftp-data || true + docker volume rm sharp-sync_ftp-data || true + docker volume rm sharp-sync_webdav-data || true + + - name: Start test services + run: | + echo "=== Starting test services with docker-compose ===" + docker compose -f docker-compose.test.yml up -d + + - name: Wait for services to be ready + run: | + echo "=== Waiting for services to be healthy ===" + docker compose -f docker-compose.test.yml ps + + echo "=== Waiting for SFTP server ===" + for i in {1..18}; do + nc -z localhost 2222 && echo "SFTP ready" && break + echo "SFTP not ready, retrying in 5 seconds... ($i/18)" + sleep 5 + done + + echo "=== Waiting for FTP server ===" + for i in {1..18}; do + nc -z localhost 21 && echo "FTP ready" && break + echo "FTP not ready, retrying in 5 seconds... ($i/18)" + sleep 5 + done + + echo "=== Waiting for LocalStack ===" + for i in {1..30}; do + curl -sf http://localhost:4566/_localstack/health && echo "LocalStack ready" && break + echo "LocalStack not ready, retrying in 10 seconds... ($i/30)" + sleep 10 + done + + echo "=== Waiting for WebDAV server ===" + WEBDAV_BASIC_READY=false + for i in {1..30}; do + if curl -sf -u testuser:testpass http://localhost:8080/ > /dev/null 2>&1; then + echo "WebDAV responding to basic requests" + WEBDAV_BASIC_READY=true + break + fi + echo "WebDAV not responding, retrying in 5 seconds... ($i/30)" + sleep 5 + done + + if [ "$WEBDAV_BASIC_READY" = "false" ]; then + echo "ERROR: WebDAV server not responding after 30 attempts" + docker compose -f docker-compose.test.yml logs webdav + exit 1 + fi + + echo "=== Checking WebDAV full functionality (MKCOL) ===" + WEBDAV_FULLY_READY=false + for i in {1..40}; do + # Show verbose output for debugging + echo "Attempt $i: Testing MKCOL operation..." + MKCOL_RESULT=$(curl -s -w "%{http_code}" -u testuser:testpass -X MKCOL http://localhost:8080/_health-check-dir/ -o /dev/null 2>&1) + echo "MKCOL response code: $MKCOL_RESULT" + + if [ "$MKCOL_RESULT" = "201" ] || [ "$MKCOL_RESULT" = "405" ]; then + echo "WebDAV is fully operational (MKCOL working)" + # Cleanup test directory + curl -sf -u testuser:testpass -X DELETE http://localhost:8080/_health-check-dir/ > /dev/null 2>&1 || true + WEBDAV_FULLY_READY=true + break + fi + + # Show container status and logs on failures + if [ $((i % 5)) -eq 0 ]; then + echo "--- WebDAV container status ---" + docker compose -f docker-compose.test.yml ps webdav + echo "--- Recent WebDAV logs ---" + docker compose -f docker-compose.test.yml logs --tail=10 webdav + fi + + echo "MKCOL not working yet, retrying in 5 seconds..." + sleep 5 + done + + if [ "$WEBDAV_FULLY_READY" = "false" ]; then + echo "ERROR: WebDAV MKCOL not working after 40 attempts" + echo "=== Full WebDAV logs ===" + docker compose -f docker-compose.test.yml logs webdav + exit 1 + fi + + echo "=== Final service status ===" + docker compose -f docker-compose.test.yml ps + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 8.0.x + - name: Restore dependencies + run: dotnet restore + - name: Check format + run: dotnet format --verify-no-changes + - name: Build + run: dotnet build --no-restore + - name: Create S3 test bucket + run: | + docker exec sharp-sync-localstack-1 awslocal s3 mb s3://test-bucket + + - name: Debug WebDAV setup + run: | + echo "=== WebDAV Container Status ===" + docker compose -f docker-compose.test.yml ps webdav + echo "" + echo "=== WebDAV Container Logs ===" + docker compose -f docker-compose.test.yml logs webdav + echo "" + echo "=== Testing WebDAV Operations ===" + echo "PROPFIND (list root):" + curl -s -w "\nHTTP Status: %{http_code}\n" -u testuser:testpass -X PROPFIND http://localhost:8080/ -H "Depth: 1" | head -30 + echo "" + echo "PUT (write test):" + echo "test content" | curl -s -w "\nHTTP Status: %{http_code}\n" -u testuser:testpass -X PUT http://localhost:8080/_debug-test.txt -d @- + echo "" + echo "DELETE (cleanup):" + curl -s -w "\nHTTP Status: %{http_code}\n" -u testuser:testpass -X DELETE http://localhost:8080/_debug-test.txt + + - name: Prepare WebDAV test root + run: | + echo "=== Creating WebDAV test root directory ===" + # Delete existing test root if present + curl -sf -u testuser:testpass -X DELETE http://localhost:8080/ci-root/ --output /dev/null 2>&1 || true + # Create fresh test root + curl -sf -u testuser:testpass -X MKCOL http://localhost:8080/ci-root/ + echo "WebDAV test root created successfully" + + - name: Test + run: dotnet test --no-build --verbosity normal + env: + SFTP_TEST_HOST: localhost + SFTP_TEST_PORT: 2222 + SFTP_TEST_USER: testuser + SFTP_TEST_PASS: testpass + SFTP_TEST_ROOT: upload + FTP_TEST_HOST: localhost + FTP_TEST_PORT: 21 + FTP_TEST_USER: testuser + FTP_TEST_PASS: testpass + FTP_TEST_ROOT: "" + S3_TEST_BUCKET: test-bucket + S3_TEST_ACCESS_KEY: test + S3_TEST_SECRET_KEY: test + S3_TEST_ENDPOINT: http://localhost:4566 + S3_TEST_PREFIX: sharpsync-tests + WEBDAV_TEST_URL: http://localhost:8080/ + WEBDAV_TEST_USER: testuser + WEBDAV_TEST_PASS: testpass + WEBDAV_TEST_ROOT: "ci-root" + + - name: Dump container logs + if: failure() + run: | + echo "=== Container logs for debugging ===" + docker compose -f docker-compose.test.yml logs + + - name: Stop test services + if: always() + run: | + docker compose -f docker-compose.test.yml down -v --remove-orphans || true diff --git a/docker-compose.test.yml b/docker-compose.test.yml index 2291147..08a0bf7 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -1,64 +1,61 @@ -services: - sftp: - image: atmoz/sftp:latest - ports: - - "2222:22" - environment: - SFTP_USERS: testuser:testpass:1001:100:upload - volumes: - - sftp-data:/home/testuser/upload - healthcheck: - test: ["CMD", "pgrep", "sshd"] - interval: 15s - timeout: 10s - retries: 10 - start_period: 15s - - ftp: - image: fauria/vsftpd:latest - ports: - - "21:21" - - "21000-21010:21000-21010" - environment: - FTP_USER: testuser - FTP_PASS: testpass - PASV_ADDRESS: localhost - PASV_MIN_PORT: 21000 - PASV_MAX_PORT: 21010 - volumes: - - ftp-data:/home/vsftpd - healthcheck: - test: ["CMD", "pgrep", "vsftpd"] - interval: 15s - timeout: 10s - retries: 10 - start_period: 15s - - localstack: - image: localstack/localstack:latest - ports: - - "4566:4566" - environment: - SERVICES: s3 - DEBUG: 0 - EDGE_PORT: 4566 - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:4566/_localstack/health"] - interval: 15s - timeout: 10s - retries: 10 - start_period: 30s - - webdav: - image: hacdias/webdav:latest - restart: unless-stopped - ports: - - "8080:80" - volumes: - - ./webdav-config.yml:/config.yaml:ro - - webdav-data:/data - -volumes: - sftp-data: - ftp-data: - webdav-data: +services: + sftp: + image: atmoz/sftp:latest + ports: + - "2222:22" + command: ["testuser:testpass:1001:100:upload"] + healthcheck: + test: ["CMD", "pgrep", "sshd"] + interval: 15s + timeout: 10s + retries: 10 + start_period: 15s + + ftp: + image: fauria/vsftpd:latest + ports: + - "21:21" + - "21000-21010:21000-21010" + environment: + FTP_USER: testuser + FTP_PASS: testpass + PASV_ADDRESS: localhost + PASV_MIN_PORT: 21000 + PASV_MAX_PORT: 21010 + volumes: + - ftp-data:/home/vsftpd + healthcheck: + test: ["CMD", "pgrep", "vsftpd"] + interval: 15s + timeout: 10s + retries: 10 + start_period: 15s + + localstack: + image: localstack/localstack:latest + ports: + - "4566:4566" + environment: + SERVICES: s3 + DEBUG: 0 + EDGE_PORT: 4566 + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:4566/_localstack/health"] + interval: 15s + timeout: 10s + retries: 10 + start_period: 30s + + webdav: + image: hacdias/webdav:latest + restart: unless-stopped + ports: + - "8080:80" + volumes: + - ./webdav-config.yml:/config.yaml:ro + - webdav-data:/data + +volumes: + sftp-data: + ftp-data: + webdav-data: