l402_middleware 2.2.1

A middleware library for rust that provides handler functions to accept microtransactions before serving ad-free content or any paid APIs.
Documentation
name: Integration Tests

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Set up Docker Compose
        run: |
          sudo curl -L "https://github.com/docker/compose/releases/download/v2.5.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
          sudo chmod +x /usr/local/bin/docker-compose
          docker-compose -f docker-compose.yml up -d bitcoind tor lndnode

      - name: Set up Rust
        uses: actions-rs/toolchain@v1
        with:
          toolchain: stable
          override: true

      - name: Install dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y pkg-config libssl-dev

      - name: Create env file
        run: |
          touch .env
          echo ROOT_KEY=ABDEGHKLMPTC >> .env
          cat .env

      - name: Run tests for LNURL
        run: cargo test --verbose
        env: 
          LN_CLIENT_TYPE: LNURL
          LNURL_ADDRESS: hello@getalby.com

      - name: Run tests for LNURL for no-accept-authenticate-required feature
        run: cargo test --verbose --features "no-accept-authenticate-required"
        env: 
          LN_CLIENT_TYPE: LNURL
          LNURL_ADDRESS: hello@getalby.com

      - name: Verify Bitcoin daemon Service and create wallet
        run: |
          wallet_name="new_wallet"
          docker exec bitcoind bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass createwallet $wallet_name
          address=$(docker exec bitcoind bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass getnewaddress $wallet_name)
          echo "New Wallet Address: $address"
          docker exec bitcoind bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass generatetoaddress 101 "$address"
          docker exec bitcoind bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass sendtoaddress bcrt1pcngfxjdkf4r2h26k52dh5nunxg8m68uf4lkfhmfjvjj6agfkm5jqmftw4e 0.0001
          docker logs bitcoind

      - name: Verify LND node
        run: |
          identity_pubkey_lndnode=$(docker exec lndnode lncli -n regtest getinfo | jq -r '.identity_pubkey')
          echo "Pubkey: $identity_pubkey_lndnode"
          docker exec bitcoind bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass -generate 5
          docker logs lndnode

      - name: Extract Macaroon and Cert Paths
        run: |
          CONTAINER_NAME="lndnode"
          
          # Wait for LND to be ready
          echo "Waiting for LND to initialize..."
          sleep 10

          MACAROON_PATH="/root/.lnd/data/chain/bitcoin/regtest/admin.macaroon"
          CERT_PATH="/root/.lnd/tls.cert"
          
          # Wait a bit more for LND to fully start
          echo "Checking if LND is ready..."
          for i in {1..30}; do
            if docker exec ${CONTAINER_NAME} lncli -n regtest getinfo > /dev/null 2>&1; then
              echo "LND is ready"
              break
            fi
            if [ $i -eq 30 ]; then
              echo "Warning: LND may not be fully ready, but continuing..."
            fi
            sleep 2
          done
          
          # Copy macaroon
          if docker exec ${CONTAINER_NAME} test -f "$MACAROON_PATH" 2>/dev/null; then
            docker cp ${CONTAINER_NAME}:$MACAROON_PATH ./admin.macaroon
            echo "✓ Extracted macaroon from $MACAROON_PATH"
          else
            echo "Error: Macaroon not found at $MACAROON_PATH"
            echo "Debug: Listing /root/.lnd structure:"
            docker exec ${CONTAINER_NAME} find /root/.lnd -type f 2>/dev/null | head -10 || true
            exit 1
          fi
          
          # Copy certificate
          if docker exec ${CONTAINER_NAME} test -f "$CERT_PATH" 2>/dev/null; then
            docker cp ${CONTAINER_NAME}:$CERT_PATH ./tls.cert
            echo "✓ Extracted certificate from $CERT_PATH"
          else
            echo "Error: Certificate not found at $CERT_PATH"
            echo "Debug: Listing /root/.lnd structure:"
            docker exec ${CONTAINER_NAME} ls -la /root/.lnd/ 2>/dev/null || true
            exit 1
          fi
          
          # Verify files were copied
          if [ -f "./admin.macaroon" ] && [ -f "./tls.cert" ]; then
            echo "✓ Successfully extracted macaroon and certificate"
            echo "MACAROON_PATH=./admin.macaroon" >> $GITHUB_ENV
            echo "CERT_PATH=./tls.cert" >> $GITHUB_ENV
          else
            echo "Error: Files were not copied successfully"
            exit 1
          fi

      - name: Run tests for LND
        run: cargo test --verbose
        env:
          LN_CLIENT_TYPE: LND
          LND_ADDRESS: 0.0.0.0:10009
          MACAROON_FILE_PATH: ${{ env.MACAROON_PATH }}
          CERT_FILE_PATH: ${{ env.CERT_PATH }}

      - name: Run tests for LND for no-accept-authenticate-required feature
        run: cargo test --verbose --features "no-accept-authenticate-required"
        env:
          LN_CLIENT_TYPE: LND
          LND_ADDRESS: 0.0.0.0:10009
          MACAROON_FILE_PATH: ${{ env.MACAROON_PATH }}
          CERT_FILE_PATH: ${{ env.CERT_PATH }}

      - name: Wait for Tor to generate onion address
        run: |
          echo "Waiting for Tor to start and generate hidden service..."
          sleep 15
          
          # Based on docker-compose.yml Tor configuration:
          # HiddenServiceDir /var/lib/tor/hidden_service/
          # So the hostname file should be at: /var/lib/tor/hidden_service/hostname
          TOR_HOSTNAME_PATH="/var/lib/tor/hidden_service/hostname"
          
          max_attempts=30
          attempt=0
          ONION_ADDRESS=""
          
          while [ $attempt -lt $max_attempts ] && [ -z "$ONION_ADDRESS" ]; do
            # Check if Tor container is running
            if ! docker ps | grep -q tor; then
              echo "Tor container is not running"
              break
            fi
            
            # Check for hostname file at the configured path
            if docker exec tor test -f "$TOR_HOSTNAME_PATH" 2>/dev/null; then
              ONION_ADDRESS=$(docker exec tor cat "$TOR_HOSTNAME_PATH" 2>/dev/null | tr -d '\n\r')
              if [ -n "$ONION_ADDRESS" ]; then
                echo "✓ Found onion address: $ONION_ADDRESS"
                echo "LND_ONION_ADDRESS=$ONION_ADDRESS:10009" >> $GITHUB_ENV
                break
              fi
            fi
            
            attempt=$((attempt + 1))
            echo "Attempt $attempt/$max_attempts: Waiting for onion address..."
            if [ $attempt -eq 10 ]; then
              echo "Debug: Checking Tor directory structure:"
              docker exec tor ls -la /var/lib/tor/ 2>/dev/null || true
            fi
            sleep 2
          done
          
          if [ -z "$LND_ONION_ADDRESS" ]; then
            echo "Warning: Could not retrieve onion address after $max_attempts attempts"
            echo "Tor hidden service may take longer to generate, or may not be configured"
            echo "SOCKS5 proxy tests will be skipped"
          else
            echo "✓ Onion address retrieved: $LND_ONION_ADDRESS"
          fi

      - name: Test LND with SOCKS5 proxy (Tor connection)
        run: |
          if [ -n "$LND_ONION_ADDRESS" ]; then
            echo "Testing LND connection through Tor SOCKS5 proxy..."
            echo "Onion address: $LND_ONION_ADDRESS"
            echo "SOCKS5 proxy: 127.0.0.1:9050"
            cargo test --verbose || echo "SOCKS5 test completed (expected limitation noted)"
          else
            echo "Skipping SOCKS5 test - onion address not available"
          fi
        env:
          LN_CLIENT_TYPE: LND
          LND_ADDRESS: ${{ env.LND_ONION_ADDRESS }}
          SOCKS5_PROXY: 127.0.0.1:9050
          MACAROON_FILE_PATH: ${{ env.MACAROON_PATH }}
          CERT_FILE_PATH: ${{ env.CERT_PATH }}

      - name: Verify SOCKS5 proxy is accessible
        run: |
          echo "Testing SOCKS5 proxy connectivity..."
          # Test if Tor SOCKS5 proxy is accessible
          curl --socks5-hostname 127.0.0.1:9050 http://example.com > /dev/null 2>&1 && \
            echo "✓ SOCKS5 proxy is working" || \
            echo "⚠ SOCKS5 proxy test failed (may be expected in CI environment)"
          # Check if Tor is running
          docker ps | grep tor && echo "✓ Tor container is running" || echo "✗ Tor container not found"

      - name: Start cln
        run: |
          docker-compose -f docker-compose.yml up -d --no-deps cln
          sleep 5

          docker logs cln
          echo "NWC_URI=$(docker exec cln lightning-cli --network=regtest nip47-create label=nwc-for-l402 budget_msat=0 | jq -r '.uri')" >> $GITHUB_ENV
          echo "BOLT12_LN_OFFER=$(docker exec cln lightning-cli --network=regtest offer any "BOLT12 Test" | jq -r '.bolt12')" >> $GITHUB_ENV

          sudo chmod 777 /cln-socket/lightning-rpc

      - name: Run tests for CLN
        run: cargo test --verbose
        env: 
          LN_CLIENT_TYPE: CLN
          CLN_LIGHTNING_RPC_FILE_PATH: /cln-socket/lightning-rpc

      - name: Run tests for CLN for no-accept-authenticate-required feature
        run: cargo test --verbose --features "no-accept-authenticate-required"
        env: 
          LN_CLIENT_TYPE: CLN
          CLN_LIGHTNING_RPC_FILE_PATH: /cln-socket/lightning-rpc

      - name: Run tests for BOLT12
        run: cargo test --verbose
        env: 
          LN_CLIENT_TYPE: BOLT12
          BOLT12_LN_OFFER: ${{ env.BOLT12_LN_OFFER }}
          CLN_LIGHTNING_RPC_FILE_PATH: /cln-socket/lightning-rpc

      - name: Run tests for BOLT12 for no-accept-authenticate-required feature
        run: cargo test --verbose --features "no-accept-authenticate-required"
        env: 
          LN_CLIENT_TYPE: BOLT12
          BOLT12_LN_OFFER: ${{ env.BOLT12_LN_OFFER }}
          CLN_LIGHTNING_RPC_FILE_PATH: /cln-socket/lightning-rpc

      - name: Run tests for NWC
        run: cargo test --verbose
        env: 
          LN_CLIENT_TYPE: NWC
          NWC_URI: ${{ env.NWC_URI }}

      - name: Start Eclair
        run: |
          docker-compose -f docker-compose.yml up -d --no-deps eclair
          echo "Waiting for Eclair to start..."
          sleep 15
          docker logs eclair

      - name: Verify Eclair node
        run: |
          echo "Checking Eclair API connectivity..."
          max_attempts=30
          attempt=0
          
          while [ $attempt -lt $max_attempts ]; do
            if curl -s --user :eclairpass http://localhost:8282/getinfo > /dev/null 2>&1; then
              echo "✓ Eclair API is accessible"
              ECLAIR_INFO=$(curl -s --user :eclairpass -X POST http://localhost:8282/getinfo)
              echo "Eclair node info: $ECLAIR_INFO"
              break
            fi
            
            attempt=$((attempt + 1))
            echo "Attempt $attempt/$max_attempts: Waiting for Eclair API..."
            sleep 2
          done
          
          if [ $attempt -eq $max_attempts ]; then
            echo "Warning: Eclair API may not be fully ready"
            docker logs eclair
          fi

      - name: Run tests for ECLAIR
        run: cargo test --verbose
        env: 
          LN_CLIENT_TYPE: ECLAIR
          ECLAIR_API_URL: http://0.0.0.0:8282
          ECLAIR_PASSWORD: eclairpass

      - name: Run tests for ECLAIR for no-accept-authenticate-required feature
        run: cargo test --verbose --features "no-accept-authenticate-required"
        env: 
          LN_CLIENT_TYPE: ECLAIR
          ECLAIR_API_URL: http://0.0.0.0:8282
          ECLAIR_PASSWORD: eclairpass

      - name: Start litd for LNC tests
        run: |
          docker-compose -f docker-compose.yml up -d --no-deps litd
          echo "Waiting for litd to start..."
          sleep 10
          docker logs litd

      - name: Generate LNC pairing phrase
        run: |
          echo "Generating LNC pairing phrase..."
          max_attempts=10
          attempt=0
          PAIRING_PHRASE=""
          
          while [ $attempt -lt $max_attempts ] && [ -z "$PAIRING_PHRASE" ]; do
            SESSION_OUTPUT=$(docker exec litd litcli --network=regtest sessions add --label="ci_test" --type=admin 2>&1 || true)
            
            echo "Debug: Session output:"
            echo "$SESSION_OUTPUT"
            
            if echo "$SESSION_OUTPUT" | grep -q "pairing_secret_mnemonic"; then
              # Extract the full mnemonic - it should be the value of pairing_secret_mnemonic field
              # The output is JSON-like, so we extract everything between the quotes after pairing_secret_mnemonic
              PAIRING_PHRASE=$(echo "$SESSION_OUTPUT" | grep "pairing_secret_mnemonic" | sed 's/.*"pairing_secret_mnemonic"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/' | tr -d '\n')
              
              # Count words to verify we got the full phrase
              WORD_COUNT=$(echo "$PAIRING_PHRASE" | wc -w)
              echo "Debug: Extracted phrase with $WORD_COUNT words: $PAIRING_PHRASE"
              
              if [ "$WORD_COUNT" -eq 10 ] && [ -n "$PAIRING_PHRASE" ]; then
                echo "✓ Generated pairing phrase with 10 words"
                echo "LNC_PAIRING_PHRASE=$PAIRING_PHRASE" >> $GITHUB_ENV
                break
              else
                echo "Warning: Got $WORD_COUNT words instead of 10, retrying..."
                PAIRING_PHRASE=""
              fi
            fi
            
            attempt=$((attempt + 1))
            echo "Attempt $attempt/$max_attempts: Waiting for litd to be ready..."
            sleep 3
          done
          
          if [ -z "$PAIRING_PHRASE" ]; then
            echo "Warning: Could not generate LNC pairing phrase"
            echo "LNC tests will be skipped"
          else
            echo "✓ LNC pairing phrase generated successfully"
          fi

      - name: Run tests for LND via LNC
        run: |
          if [ -n "$LNC_PAIRING_PHRASE" ]; then
            echo "Running LND tests via LNC (Lightning Node Connect)..."
            echo "LNC will be used instead of traditional gRPC connection"
            cargo test --verbose
          else
            echo "Skipping LNC tests - pairing phrase not available"
          fi
        env:
          LN_CLIENT_TYPE: LND
          LNC_PAIRING_PHRASE: ${{ env.LNC_PAIRING_PHRASE }}
          LNC_MAILBOX_SERVER: mailbox.terminal.lightning.today:443