mirror of
				https://github.com/dgtlmoon/changedetection.io.git
				synced 2025-11-04 08:34:57 +00:00 
			
		
		
		
	Compare commits
	
		
			7 Commits
		
	
	
		
			multiple-t
			...
			2408-user-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					606e29f7ee | ||
| 
						 | 
					9e6def43bd | ||
| 
						 | 
					21f8ff982b | ||
| 
						 | 
					10e4126eb5 | ||
| 
						 | 
					de18f0b5d0 | ||
| 
						 | 
					78ddb3b546 | ||
| 
						 | 
					e528cf109a | 
							
								
								
									
										221
									
								
								.github/workflows/test-only.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										221
									
								
								.github/workflows/test-only.yml
									
									
									
									
										vendored
									
									
								
							@@ -4,10 +4,17 @@ name: ChangeDetection.io App Test
 | 
				
			|||||||
on: [push, pull_request]
 | 
					on: [push, pull_request]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  lint-code:
 | 
					  test-application:
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/checkout@v4
 | 
					      - uses: actions/checkout@v4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # Mainly just for link/flake8
 | 
				
			||||||
 | 
					      - name: Set up Python 3.11
 | 
				
			||||||
 | 
					        uses: actions/setup-python@v5
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          python-version: '3.11'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Lint with flake8
 | 
					      - name: Lint with flake8
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          pip3 install flake8
 | 
					          pip3 install flake8
 | 
				
			||||||
@@ -16,24 +23,202 @@ jobs:
 | 
				
			|||||||
          # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
 | 
					          # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
 | 
				
			||||||
          flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
 | 
					          flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  test-application-3-10:
 | 
					      - name: Spin up ancillary testable services
 | 
				
			||||||
    needs: lint-code
 | 
					        run: |
 | 
				
			||||||
    uses: ./.github/workflows/test-stack-reusable-workflow.yml
 | 
					          
 | 
				
			||||||
    with:
 | 
					          docker network create changedet-network
 | 
				
			||||||
      python-version: '3.10'
 | 
					          
 | 
				
			||||||
 | 
					          # Selenium
 | 
				
			||||||
 | 
					          docker run --network changedet-network -d --hostname selenium  -p 4444:4444 --rm --shm-size="2g"  selenium/standalone-chrome:4
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          # SocketPuppetBrowser + Extra for custom browser test
 | 
				
			||||||
 | 
					          docker run --network changedet-network -d -e "LOG_LEVEL=TRACE" --cap-add=SYS_ADMIN --name sockpuppetbrowser --hostname sockpuppetbrowser --rm -p 3000:3000 dgtlmoon/sockpuppetbrowser:latest                    
 | 
				
			||||||
 | 
					          docker run --network changedet-network -d -e "LOG_LEVEL=TRACE" --cap-add=SYS_ADMIN --name sockpuppetbrowser-custom-url --hostname sockpuppetbrowser-custom-url  -p 3001:3000 --rm dgtlmoon/sockpuppetbrowser:latest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Build changedetection.io container for testing
 | 
				
			||||||
 | 
					        run: |         
 | 
				
			||||||
 | 
					          # Build a changedetection.io container and start testing inside
 | 
				
			||||||
 | 
					          docker build --build-arg LOGGER_LEVEL=TRACE -t test-changedetectionio .
 | 
				
			||||||
 | 
					          # Debug info
 | 
				
			||||||
 | 
					          docker run test-changedetectionio  bash -c 'pip list'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Spin up ancillary SMTP+Echo message test server
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          # Debug SMTP server/echo message back server
 | 
				
			||||||
 | 
					          docker run --network changedet-network -d -p 11025:11025 -p 11080:11080  --hostname mailserver test-changedetectionio  bash -c 'python changedetectionio/tests/smtp/smtp-test-server.py' 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Show docker container state and other debug info
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          set -x
 | 
				
			||||||
 | 
					          echo "Running processes in docker..."
 | 
				
			||||||
 | 
					          docker ps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Test built container with Pytest (generally as requests/plaintext fetching)
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          # Unit tests
 | 
				
			||||||
 | 
					          echo "run test with unittest"
 | 
				
			||||||
 | 
					          docker run test-changedetectionio  bash -c 'python3 -m unittest changedetectionio.tests.unit.test_notification_diff'
 | 
				
			||||||
 | 
					          docker run test-changedetectionio  bash -c 'python3 -m unittest changedetectionio.tests.unit.test_watch_model'
 | 
				
			||||||
 | 
					          docker run test-changedetectionio  bash -c 'python3 -m unittest changedetectionio.tests.unit.test_jinja2_security'
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          # All tests
 | 
				
			||||||
 | 
					          echo "run test with pytest"
 | 
				
			||||||
 | 
					          # The default pytest logger_level is TRACE
 | 
				
			||||||
 | 
					          # To change logger_level for pytest(test/conftest.py),
 | 
				
			||||||
 | 
					          # append the docker option. e.g. '-e LOGGER_LEVEL=DEBUG'
 | 
				
			||||||
 | 
					          docker run --name test-cdio-basic-tests --network changedet-network  test-changedetectionio  bash -c 'cd changedetectionio && ./run_basic_tests.sh'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# PLAYWRIGHT/NODE-> CDP
 | 
				
			||||||
 | 
					      - name: Playwright and SocketPuppetBrowser - Specific tests in built container
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          # Playwright via Sockpuppetbrowser fetch
 | 
				
			||||||
 | 
					          # tests/visualselector/test_fetch_data.py will do browser steps  
 | 
				
			||||||
 | 
					          docker run --rm -e "FLASK_SERVER_NAME=cdio" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network --hostname=cdio test-changedetectionio  bash -c 'cd changedetectionio;pytest --live-server-host=0.0.0.0 --live-server-port=5004 tests/fetchers/test_content.py'
 | 
				
			||||||
 | 
					          docker run --rm -e "FLASK_SERVER_NAME=cdio" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network --hostname=cdio test-changedetectionio  bash -c 'cd changedetectionio;pytest --live-server-host=0.0.0.0 --live-server-port=5004 tests/test_errorhandling.py'
 | 
				
			||||||
 | 
					          docker run --rm -e "FLASK_SERVER_NAME=cdio" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network --hostname=cdio test-changedetectionio  bash -c 'cd changedetectionio;pytest --live-server-host=0.0.0.0 --live-server-port=5004 tests/visualselector/test_fetch_data.py'
 | 
				
			||||||
 | 
					          docker run --rm -e "FLASK_SERVER_NAME=cdio" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network --hostname=cdio test-changedetectionio  bash -c 'cd changedetectionio;pytest --live-server-host=0.0.0.0 --live-server-port=5004 tests/fetchers/test_custom_js_before_content.py'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  test-application-3-11:
 | 
					      - name: Playwright and SocketPuppetBrowser - Headers and requests
 | 
				
			||||||
    needs: lint-code
 | 
					        run: |       
 | 
				
			||||||
    uses: ./.github/workflows/test-stack-reusable-workflow.yml
 | 
					          # Settings headers playwright tests - Call back in from Sockpuppetbrowser, check headers
 | 
				
			||||||
    with:
 | 
					          docker run --name "changedet" --hostname changedet --rm -e "FLASK_SERVER_NAME=changedet" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000?dumpio=true" --network changedet-network test-changedetectionio  bash -c 'cd changedetectionio; pytest --live-server-host=0.0.0.0  --live-server-port=5004 tests/test_request.py'
 | 
				
			||||||
      python-version: '3.11'
 | 
					 | 
				
			||||||
      skip-pypuppeteer: true
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  test-application-3-12:
 | 
					      - name: Playwright and SocketPuppetBrowser - Restock detection
 | 
				
			||||||
    needs: lint-code
 | 
					        run: |                            
 | 
				
			||||||
    uses: ./.github/workflows/test-stack-reusable-workflow.yml
 | 
					          # restock detection via playwright - added name=changedet here so that playwright and sockpuppetbrowser can connect to it
 | 
				
			||||||
    with:
 | 
					          docker run --rm --name "changedet" -e "FLASK_SERVER_NAME=changedet" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network test-changedetectionio  bash -c 'cd changedetectionio;pytest --live-server-port=5004 --live-server-host=0.0.0.0 tests/restock/test_restock.py'
 | 
				
			||||||
      python-version: '3.12'
 | 
					 | 
				
			||||||
      skip-pypuppeteer: true
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# STRAIGHT TO CDP
 | 
				
			||||||
 | 
					      - name: Pyppeteer and SocketPuppetBrowser - Specific tests in built container
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          # Playwright via Sockpuppetbrowser fetch 
 | 
				
			||||||
 | 
					          docker run --rm -e "FLASK_SERVER_NAME=cdio" -e "FAST_PUPPETEER_CHROME_FETCHER=True" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network --hostname=cdio test-changedetectionio  bash -c 'cd changedetectionio;pytest --live-server-host=0.0.0.0 --live-server-port=5004 tests/fetchers/test_content.py'
 | 
				
			||||||
 | 
					          docker run --rm -e "FLASK_SERVER_NAME=cdio" -e "FAST_PUPPETEER_CHROME_FETCHER=True" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network --hostname=cdio test-changedetectionio  bash -c 'cd changedetectionio;pytest --live-server-host=0.0.0.0 --live-server-port=5004 tests/test_errorhandling.py'
 | 
				
			||||||
 | 
					          docker run --rm -e "FLASK_SERVER_NAME=cdio" -e "FAST_PUPPETEER_CHROME_FETCHER=True" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network --hostname=cdio test-changedetectionio  bash -c 'cd changedetectionio;pytest --live-server-host=0.0.0.0 --live-server-port=5004 tests/visualselector/test_fetch_data.py'
 | 
				
			||||||
 | 
					          docker run --rm -e "FLASK_SERVER_NAME=cdio" -e "FAST_PUPPETEER_CHROME_FETCHER=True" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network --hostname=cdio test-changedetectionio  bash -c 'cd changedetectionio;pytest --live-server-host=0.0.0.0 --live-server-port=5004 tests/fetchers/test_custom_js_before_content.py'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Pyppeteer and SocketPuppetBrowser - Headers and requests checks
 | 
				
			||||||
 | 
					        run: |       
 | 
				
			||||||
 | 
					          # Settings headers playwright tests - Call back in from Sockpuppetbrowser, check headers
 | 
				
			||||||
 | 
					          docker run --name "changedet" --hostname changedet --rm  -e "FAST_PUPPETEER_CHROME_FETCHER=True" -e "FLASK_SERVER_NAME=changedet" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000?dumpio=true" --network changedet-network test-changedetectionio  bash -c 'cd changedetectionio; pytest --live-server-host=0.0.0.0  --live-server-port=5004 tests/test_request.py'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Pyppeteer and SocketPuppetBrowser - Restock detection
 | 
				
			||||||
 | 
					        run: |                            
 | 
				
			||||||
 | 
					          # restock detection via playwright - added name=changedet here so that playwright and sockpuppetbrowser can connect to it
 | 
				
			||||||
 | 
					          docker run --rm --name "changedet" -e "FLASK_SERVER_NAME=changedet"  -e "FAST_PUPPETEER_CHROME_FETCHER=True"  -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network test-changedetectionio  bash -c 'cd changedetectionio;pytest --live-server-port=5004 --live-server-host=0.0.0.0 tests/restock/test_restock.py'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# SELENIUM
 | 
				
			||||||
 | 
					      - name: Specific tests in built container for Selenium
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          # Selenium fetch
 | 
				
			||||||
 | 
					          docker run --rm -e "WEBDRIVER_URL=http://selenium:4444/wd/hub" --network changedet-network test-changedetectionio  bash -c 'cd changedetectionio;pytest tests/fetchers/test_content.py && pytest tests/test_errorhandling.py'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Specific tests in built container for headers and requests checks with Selenium
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          docker run --name "changedet" --hostname changedet --rm -e "FLASK_SERVER_NAME=changedet" -e "WEBDRIVER_URL=http://selenium:4444/wd/hub" --network changedet-network test-changedetectionio  bash -c 'cd changedetectionio; pytest --live-server-host=0.0.0.0  --live-server-port=5004 tests/test_request.py'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# OTHER STUFF
 | 
				
			||||||
 | 
					      - name: Test SMTP notification mime types
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          # SMTP content types - needs the 'Debug SMTP server/echo message back server' container from above
 | 
				
			||||||
 | 
					          docker run --rm  --network changedet-network test-changedetectionio bash -c 'cd changedetectionio;pytest tests/smtp/test_notification_smtp.py'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # @todo Add a test via playwright/puppeteer
 | 
				
			||||||
 | 
					      # squid with auth is tested in run_proxy_tests.sh -> tests/proxy_list/test_select_custom_proxy.py
 | 
				
			||||||
 | 
					      - name: Test proxy squid style interaction
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          cd changedetectionio
 | 
				
			||||||
 | 
					          ./run_proxy_tests.sh
 | 
				
			||||||
 | 
					          cd ..
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Test proxy SOCKS5 style interaction
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          cd changedetectionio
 | 
				
			||||||
 | 
					          ./run_socks_proxy_tests.sh
 | 
				
			||||||
 | 
					          cd ..
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Test custom browser URL
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          cd changedetectionio
 | 
				
			||||||
 | 
					          ./run_custom_browser_url_tests.sh
 | 
				
			||||||
 | 
					          cd ..
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Test changedetection.io container starts+runs basically without error
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          docker run --name test-changedetectionio -p 5556:5000  -d test-changedetectionio
 | 
				
			||||||
 | 
					          sleep 3
 | 
				
			||||||
 | 
					          # Should return 0 (no error) when grep finds it
 | 
				
			||||||
 | 
					          curl --retry-connrefused --retry 6  -s http://localhost:5556 |grep -q checkbox-uuid
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          # and IPv6
 | 
				
			||||||
 | 
					          curl --retry-connrefused --retry 6  -s -g -6 "http://[::1]:5556"|grep -q checkbox-uuid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          # Check whether TRACE log is enabled.
 | 
				
			||||||
 | 
					          # Also, check whether TRACE is came from STDERR
 | 
				
			||||||
 | 
					          docker logs test-changedetectionio 2>&1 1>/dev/null | grep 'TRACE log is enabled' || exit 1
 | 
				
			||||||
 | 
					          # Check whether DEBUG is came from STDOUT
 | 
				
			||||||
 | 
					          docker logs test-changedetectionio 2>/dev/null | grep 'DEBUG' || exit 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          docker kill test-changedetectionio
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Test changedetection.io SIGTERM and SIGINT signal shutdown
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          echo SIGINT Shutdown request test
 | 
				
			||||||
 | 
					          docker run --name sig-test -d test-changedetectionio
 | 
				
			||||||
 | 
					          sleep 3
 | 
				
			||||||
 | 
					          echo ">>> Sending SIGINT to sig-test container"
 | 
				
			||||||
 | 
					          docker kill --signal=SIGINT sig-test
 | 
				
			||||||
 | 
					          sleep 3
 | 
				
			||||||
 | 
					          # invert the check (it should be not 0/not running)
 | 
				
			||||||
 | 
					          docker ps
 | 
				
			||||||
 | 
					          # check signal catch(STDERR) log. Because of
 | 
				
			||||||
 | 
					          # changedetectionio/__init__.py: logger.add(sys.stderr, level=logger_level)
 | 
				
			||||||
 | 
					          docker logs sig-test 2>&1 | grep 'Shutdown: Got Signal - SIGINT' || exit 1
 | 
				
			||||||
 | 
					          test -z "`docker ps|grep sig-test`"
 | 
				
			||||||
 | 
					          if [ $? -ne 0 ]
 | 
				
			||||||
 | 
					          then
 | 
				
			||||||
 | 
					            echo "Looks like container was running when it shouldnt be"
 | 
				
			||||||
 | 
					            docker ps
 | 
				
			||||||
 | 
					            exit 1
 | 
				
			||||||
 | 
					          fi
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          # @todo - scan the container log to see the right "graceful shutdown" text exists 
 | 
				
			||||||
 | 
					          docker rm sig-test
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          echo SIGTERM Shutdown request test
 | 
				
			||||||
 | 
					          docker run --name sig-test -d test-changedetectionio
 | 
				
			||||||
 | 
					          sleep 3
 | 
				
			||||||
 | 
					          echo ">>> Sending SIGTERM to sig-test container"
 | 
				
			||||||
 | 
					          docker kill --signal=SIGTERM sig-test
 | 
				
			||||||
 | 
					          sleep 3
 | 
				
			||||||
 | 
					          # invert the check (it should be not 0/not running)
 | 
				
			||||||
 | 
					          docker ps
 | 
				
			||||||
 | 
					          # check signal catch(STDERR) log. Because of
 | 
				
			||||||
 | 
					          # changedetectionio/__init__.py: logger.add(sys.stderr, level=logger_level)
 | 
				
			||||||
 | 
					          docker logs sig-test 2>&1 | grep 'Shutdown: Got Signal - SIGTERM' || exit 1
 | 
				
			||||||
 | 
					          test -z "`docker ps|grep sig-test`"
 | 
				
			||||||
 | 
					          if [ $? -ne 0 ]
 | 
				
			||||||
 | 
					          then
 | 
				
			||||||
 | 
					            echo "Looks like container was running when it shouldnt be"
 | 
				
			||||||
 | 
					            docker ps
 | 
				
			||||||
 | 
					            exit 1
 | 
				
			||||||
 | 
					          fi
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          # @todo - scan the container log to see the right "graceful shutdown" text exists           
 | 
				
			||||||
 | 
					          docker rm sig-test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Dump container log
 | 
				
			||||||
 | 
					        if: always()
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          mkdir output-logs
 | 
				
			||||||
 | 
					          docker logs test-cdio-basic-tests > output-logs/test-cdio-basic-tests-stdout.txt
 | 
				
			||||||
 | 
					          docker logs test-cdio-basic-tests 2> output-logs/test-cdio-basic-tests-stderr.txt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Store container log
 | 
				
			||||||
 | 
					        if: always()
 | 
				
			||||||
 | 
					        uses: actions/upload-artifact@v4
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          name: test-cdio-basic-tests-output
 | 
				
			||||||
 | 
					          path: output-logs
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										239
									
								
								.github/workflows/test-stack-reusable-workflow.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										239
									
								
								.github/workflows/test-stack-reusable-workflow.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,239 +0,0 @@
 | 
				
			|||||||
name: ChangeDetection.io App Test
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
on:
 | 
					 | 
				
			||||||
  workflow_call:
 | 
					 | 
				
			||||||
    inputs:
 | 
					 | 
				
			||||||
      python-version:
 | 
					 | 
				
			||||||
        description: 'Python version to use'
 | 
					 | 
				
			||||||
        required: true
 | 
					 | 
				
			||||||
        type: string
 | 
					 | 
				
			||||||
        default: '3.10'
 | 
					 | 
				
			||||||
      skip-pypuppeteer:
 | 
					 | 
				
			||||||
        description: 'Skip PyPuppeteer (not supported in 3.11/3.12)'
 | 
					 | 
				
			||||||
        required: false
 | 
					 | 
				
			||||||
        type: boolean
 | 
					 | 
				
			||||||
        default: false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
jobs:
 | 
					 | 
				
			||||||
  test-application:
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					 | 
				
			||||||
    env:
 | 
					 | 
				
			||||||
      PYTHON_VERSION: ${{ inputs.python-version }}
 | 
					 | 
				
			||||||
    steps:
 | 
					 | 
				
			||||||
      - uses: actions/checkout@v4
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      # Mainly just for link/flake8
 | 
					 | 
				
			||||||
      - name: Set up Python ${{ env.PYTHON_VERSION }}
 | 
					 | 
				
			||||||
        uses: actions/setup-python@v5
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          python-version: ${{ env.PYTHON_VERSION }}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Build changedetection.io container for testing under Python ${{ env.PYTHON_VERSION }}
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          echo "---- Building for Python ${{ env.PYTHON_VERSION }} -----"
 | 
					 | 
				
			||||||
          # Build a changedetection.io container and start testing inside
 | 
					 | 
				
			||||||
          docker build --build-arg PYTHON_VERSION=${{ env.PYTHON_VERSION }} --build-arg LOGGER_LEVEL=TRACE -t test-changedetectionio .
 | 
					 | 
				
			||||||
          # Debug info
 | 
					 | 
				
			||||||
          docker run test-changedetectionio  bash -c 'pip list'         
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: We should be Python ${{ env.PYTHON_VERSION }} ...
 | 
					 | 
				
			||||||
        run: |         
 | 
					 | 
				
			||||||
          docker run test-changedetectionio  bash -c 'python3 --version'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Spin up ancillary testable services
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          
 | 
					 | 
				
			||||||
          docker network create changedet-network
 | 
					 | 
				
			||||||
          
 | 
					 | 
				
			||||||
          # Selenium
 | 
					 | 
				
			||||||
          docker run --network changedet-network -d --hostname selenium  -p 4444:4444 --rm --shm-size="2g"  selenium/standalone-chrome:4
 | 
					 | 
				
			||||||
          
 | 
					 | 
				
			||||||
          # SocketPuppetBrowser + Extra for custom browser test
 | 
					 | 
				
			||||||
          docker run --network changedet-network -d -e "LOG_LEVEL=TRACE" --cap-add=SYS_ADMIN --name sockpuppetbrowser --hostname sockpuppetbrowser --rm -p 3000:3000 dgtlmoon/sockpuppetbrowser:latest                    
 | 
					 | 
				
			||||||
          docker run --network changedet-network -d -e "LOG_LEVEL=TRACE" --cap-add=SYS_ADMIN --name sockpuppetbrowser-custom-url --hostname sockpuppetbrowser-custom-url  -p 3001:3000 --rm dgtlmoon/sockpuppetbrowser:latest
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Spin up ancillary SMTP+Echo message test server
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          # Debug SMTP server/echo message back server
 | 
					 | 
				
			||||||
          docker run --network changedet-network -d -p 11025:11025 -p 11080:11080  --hostname mailserver test-changedetectionio  bash -c 'pip3 install aiosmtpd && python changedetectionio/tests/smtp/smtp-test-server.py'
 | 
					 | 
				
			||||||
          docker ps
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Show docker container state and other debug info
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          set -x
 | 
					 | 
				
			||||||
          echo "Running processes in docker..."
 | 
					 | 
				
			||||||
          docker ps
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Test built container with Pytest (generally as requests/plaintext fetching)
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          # Unit tests
 | 
					 | 
				
			||||||
          echo "run test with unittest"
 | 
					 | 
				
			||||||
          docker run test-changedetectionio  bash -c 'python3 -m unittest changedetectionio.tests.unit.test_notification_diff'
 | 
					 | 
				
			||||||
          docker run test-changedetectionio  bash -c 'python3 -m unittest changedetectionio.tests.unit.test_watch_model'
 | 
					 | 
				
			||||||
          docker run test-changedetectionio  bash -c 'python3 -m unittest changedetectionio.tests.unit.test_jinja2_security'
 | 
					 | 
				
			||||||
          
 | 
					 | 
				
			||||||
          # All tests
 | 
					 | 
				
			||||||
          echo "run test with pytest"
 | 
					 | 
				
			||||||
          # The default pytest logger_level is TRACE
 | 
					 | 
				
			||||||
          # To change logger_level for pytest(test/conftest.py),
 | 
					 | 
				
			||||||
          # append the docker option. e.g. '-e LOGGER_LEVEL=DEBUG'
 | 
					 | 
				
			||||||
          docker run --name test-cdio-basic-tests --network changedet-network  test-changedetectionio  bash -c 'cd changedetectionio && ./run_basic_tests.sh'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# PLAYWRIGHT/NODE-> CDP
 | 
					 | 
				
			||||||
      - name: Playwright and SocketPuppetBrowser - Specific tests in built container
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          # Playwright via Sockpuppetbrowser fetch
 | 
					 | 
				
			||||||
          # tests/visualselector/test_fetch_data.py will do browser steps  
 | 
					 | 
				
			||||||
          docker run --rm -e "FLASK_SERVER_NAME=cdio" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network --hostname=cdio test-changedetectionio  bash -c 'cd changedetectionio;pytest --live-server-host=0.0.0.0 --live-server-port=5004 tests/fetchers/test_content.py'
 | 
					 | 
				
			||||||
          docker run --rm -e "FLASK_SERVER_NAME=cdio" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network --hostname=cdio test-changedetectionio  bash -c 'cd changedetectionio;pytest --live-server-host=0.0.0.0 --live-server-port=5004 tests/test_errorhandling.py'
 | 
					 | 
				
			||||||
          docker run --rm -e "FLASK_SERVER_NAME=cdio" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network --hostname=cdio test-changedetectionio  bash -c 'cd changedetectionio;pytest --live-server-host=0.0.0.0 --live-server-port=5004 tests/visualselector/test_fetch_data.py'
 | 
					 | 
				
			||||||
          docker run --rm -e "FLASK_SERVER_NAME=cdio" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network --hostname=cdio test-changedetectionio  bash -c 'cd changedetectionio;pytest --live-server-host=0.0.0.0 --live-server-port=5004 tests/fetchers/test_custom_js_before_content.py'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Playwright and SocketPuppetBrowser - Headers and requests
 | 
					 | 
				
			||||||
        run: |       
 | 
					 | 
				
			||||||
          # Settings headers playwright tests - Call back in from Sockpuppetbrowser, check headers
 | 
					 | 
				
			||||||
          docker run --name "changedet" --hostname changedet --rm -e "FLASK_SERVER_NAME=changedet" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000?dumpio=true" --network changedet-network test-changedetectionio  bash -c 'cd changedetectionio; pytest --live-server-host=0.0.0.0  --live-server-port=5004 tests/test_request.py'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Playwright and SocketPuppetBrowser - Restock detection
 | 
					 | 
				
			||||||
        run: |                            
 | 
					 | 
				
			||||||
          # restock detection via playwright - added name=changedet here so that playwright and sockpuppetbrowser can connect to it
 | 
					 | 
				
			||||||
          docker run --rm --name "changedet" -e "FLASK_SERVER_NAME=changedet" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network test-changedetectionio  bash -c 'cd changedetectionio;pytest --live-server-port=5004 --live-server-host=0.0.0.0 tests/restock/test_restock.py'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# STRAIGHT TO CDP
 | 
					 | 
				
			||||||
      - name: Pyppeteer and SocketPuppetBrowser - Specific tests in built container
 | 
					 | 
				
			||||||
        if: ${{ inputs.skip-pypuppeteer == false }}
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          # Playwright via Sockpuppetbrowser fetch 
 | 
					 | 
				
			||||||
          docker run --rm -e "FLASK_SERVER_NAME=cdio" -e "FAST_PUPPETEER_CHROME_FETCHER=True" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network --hostname=cdio test-changedetectionio  bash -c 'cd changedetectionio;pytest --live-server-host=0.0.0.0 --live-server-port=5004 tests/fetchers/test_content.py'
 | 
					 | 
				
			||||||
          docker run --rm -e "FLASK_SERVER_NAME=cdio" -e "FAST_PUPPETEER_CHROME_FETCHER=True" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network --hostname=cdio test-changedetectionio  bash -c 'cd changedetectionio;pytest --live-server-host=0.0.0.0 --live-server-port=5004 tests/test_errorhandling.py'
 | 
					 | 
				
			||||||
          docker run --rm -e "FLASK_SERVER_NAME=cdio" -e "FAST_PUPPETEER_CHROME_FETCHER=True" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network --hostname=cdio test-changedetectionio  bash -c 'cd changedetectionio;pytest --live-server-host=0.0.0.0 --live-server-port=5004 tests/visualselector/test_fetch_data.py'
 | 
					 | 
				
			||||||
          docker run --rm -e "FLASK_SERVER_NAME=cdio" -e "FAST_PUPPETEER_CHROME_FETCHER=True" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network --hostname=cdio test-changedetectionio  bash -c 'cd changedetectionio;pytest --live-server-host=0.0.0.0 --live-server-port=5004 tests/fetchers/test_custom_js_before_content.py'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Pyppeteer and SocketPuppetBrowser - Headers and requests checks
 | 
					 | 
				
			||||||
        if: ${{ inputs.skip-pypuppeteer == false }}
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          # Settings headers playwright tests - Call back in from Sockpuppetbrowser, check headers
 | 
					 | 
				
			||||||
          docker run --name "changedet" --hostname changedet --rm  -e "FAST_PUPPETEER_CHROME_FETCHER=True" -e "FLASK_SERVER_NAME=changedet" -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000?dumpio=true" --network changedet-network test-changedetectionio  bash -c 'cd changedetectionio; pytest --live-server-host=0.0.0.0  --live-server-port=5004 tests/test_request.py'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Pyppeteer and SocketPuppetBrowser - Restock detection
 | 
					 | 
				
			||||||
        if: ${{ inputs.skip-pypuppeteer == false }}
 | 
					 | 
				
			||||||
        run: |                            
 | 
					 | 
				
			||||||
          # restock detection via playwright - added name=changedet here so that playwright and sockpuppetbrowser can connect to it
 | 
					 | 
				
			||||||
          docker run --rm --name "changedet" -e "FLASK_SERVER_NAME=changedet"  -e "FAST_PUPPETEER_CHROME_FETCHER=True"  -e "PLAYWRIGHT_DRIVER_URL=ws://sockpuppetbrowser:3000" --network changedet-network test-changedetectionio  bash -c 'cd changedetectionio;pytest --live-server-port=5004 --live-server-host=0.0.0.0 tests/restock/test_restock.py'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# SELENIUM
 | 
					 | 
				
			||||||
      - name: Specific tests in built container for Selenium
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          # Selenium fetch
 | 
					 | 
				
			||||||
          docker run --rm -e "WEBDRIVER_URL=http://selenium:4444/wd/hub" --network changedet-network test-changedetectionio  bash -c 'cd changedetectionio;pytest tests/fetchers/test_content.py && pytest tests/test_errorhandling.py'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Specific tests in built container for headers and requests checks with Selenium
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          docker run --name "changedet" --hostname changedet --rm -e "FLASK_SERVER_NAME=changedet" -e "WEBDRIVER_URL=http://selenium:4444/wd/hub" --network changedet-network test-changedetectionio  bash -c 'cd changedetectionio; pytest --live-server-host=0.0.0.0  --live-server-port=5004 tests/test_request.py'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# OTHER STUFF
 | 
					 | 
				
			||||||
      - name: Test SMTP notification mime types
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          # SMTP content types - needs the 'Debug SMTP server/echo message back server' container from above
 | 
					 | 
				
			||||||
          # "mailserver" hostname defined above
 | 
					 | 
				
			||||||
          docker run --rm --network changedet-network test-changedetectionio bash -c 'cd changedetectionio;pytest tests/smtp/test_notification_smtp.py'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      # @todo Add a test via playwright/puppeteer
 | 
					 | 
				
			||||||
      # squid with auth is tested in run_proxy_tests.sh -> tests/proxy_list/test_select_custom_proxy.py
 | 
					 | 
				
			||||||
      - name: Test proxy squid style interaction
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          cd changedetectionio
 | 
					 | 
				
			||||||
          ./run_proxy_tests.sh
 | 
					 | 
				
			||||||
          cd ..
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Test proxy SOCKS5 style interaction
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          cd changedetectionio
 | 
					 | 
				
			||||||
          ./run_socks_proxy_tests.sh
 | 
					 | 
				
			||||||
          cd ..
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Test custom browser URL
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          cd changedetectionio
 | 
					 | 
				
			||||||
          ./run_custom_browser_url_tests.sh
 | 
					 | 
				
			||||||
          cd ..
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Test changedetection.io container starts+runs basically without error
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          docker run --name test-changedetectionio -p 5556:5000  -d test-changedetectionio
 | 
					 | 
				
			||||||
          sleep 3
 | 
					 | 
				
			||||||
          # Should return 0 (no error) when grep finds it
 | 
					 | 
				
			||||||
          curl --retry-connrefused --retry 6  -s http://localhost:5556 |grep -q checkbox-uuid
 | 
					 | 
				
			||||||
          
 | 
					 | 
				
			||||||
          # and IPv6
 | 
					 | 
				
			||||||
          curl --retry-connrefused --retry 6  -s -g -6 "http://[::1]:5556"|grep -q checkbox-uuid
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          # Check whether TRACE log is enabled.
 | 
					 | 
				
			||||||
          # Also, check whether TRACE is came from STDERR
 | 
					 | 
				
			||||||
          docker logs test-changedetectionio 2>&1 1>/dev/null | grep 'TRACE log is enabled' || exit 1
 | 
					 | 
				
			||||||
          # Check whether DEBUG is came from STDOUT
 | 
					 | 
				
			||||||
          docker logs test-changedetectionio 2>/dev/null | grep 'DEBUG' || exit 1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          docker kill test-changedetectionio
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Test changedetection.io SIGTERM and SIGINT signal shutdown
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          
 | 
					 | 
				
			||||||
          echo SIGINT Shutdown request test
 | 
					 | 
				
			||||||
          docker run --name sig-test -d test-changedetectionio
 | 
					 | 
				
			||||||
          sleep 3
 | 
					 | 
				
			||||||
          echo ">>> Sending SIGINT to sig-test container"
 | 
					 | 
				
			||||||
          docker kill --signal=SIGINT sig-test
 | 
					 | 
				
			||||||
          sleep 3
 | 
					 | 
				
			||||||
          # invert the check (it should be not 0/not running)
 | 
					 | 
				
			||||||
          docker ps
 | 
					 | 
				
			||||||
          # check signal catch(STDERR) log. Because of
 | 
					 | 
				
			||||||
          # changedetectionio/__init__.py: logger.add(sys.stderr, level=logger_level)
 | 
					 | 
				
			||||||
          docker logs sig-test 2>&1 | grep 'Shutdown: Got Signal - SIGINT' || exit 1
 | 
					 | 
				
			||||||
          test -z "`docker ps|grep sig-test`"
 | 
					 | 
				
			||||||
          if [ $? -ne 0 ]
 | 
					 | 
				
			||||||
          then
 | 
					 | 
				
			||||||
            echo "Looks like container was running when it shouldnt be"
 | 
					 | 
				
			||||||
            docker ps
 | 
					 | 
				
			||||||
            exit 1
 | 
					 | 
				
			||||||
          fi
 | 
					 | 
				
			||||||
          
 | 
					 | 
				
			||||||
          # @todo - scan the container log to see the right "graceful shutdown" text exists 
 | 
					 | 
				
			||||||
          docker rm sig-test
 | 
					 | 
				
			||||||
          
 | 
					 | 
				
			||||||
          echo SIGTERM Shutdown request test
 | 
					 | 
				
			||||||
          docker run --name sig-test -d test-changedetectionio
 | 
					 | 
				
			||||||
          sleep 3
 | 
					 | 
				
			||||||
          echo ">>> Sending SIGTERM to sig-test container"
 | 
					 | 
				
			||||||
          docker kill --signal=SIGTERM sig-test
 | 
					 | 
				
			||||||
          sleep 3
 | 
					 | 
				
			||||||
          # invert the check (it should be not 0/not running)
 | 
					 | 
				
			||||||
          docker ps
 | 
					 | 
				
			||||||
          # check signal catch(STDERR) log. Because of
 | 
					 | 
				
			||||||
          # changedetectionio/__init__.py: logger.add(sys.stderr, level=logger_level)
 | 
					 | 
				
			||||||
          docker logs sig-test 2>&1 | grep 'Shutdown: Got Signal - SIGTERM' || exit 1
 | 
					 | 
				
			||||||
          test -z "`docker ps|grep sig-test`"
 | 
					 | 
				
			||||||
          if [ $? -ne 0 ]
 | 
					 | 
				
			||||||
          then
 | 
					 | 
				
			||||||
            echo "Looks like container was running when it shouldnt be"
 | 
					 | 
				
			||||||
            docker ps
 | 
					 | 
				
			||||||
            exit 1
 | 
					 | 
				
			||||||
          fi
 | 
					 | 
				
			||||||
          
 | 
					 | 
				
			||||||
          # @todo - scan the container log to see the right "graceful shutdown" text exists           
 | 
					 | 
				
			||||||
          docker rm sig-test
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Dump container log
 | 
					 | 
				
			||||||
        if: always()
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          mkdir output-logs
 | 
					 | 
				
			||||||
          docker logs test-cdio-basic-tests > output-logs/test-cdio-basic-tests-stdout-${{ env.PYTHON_VERSION }}.txt
 | 
					 | 
				
			||||||
          docker logs test-cdio-basic-tests 2> output-logs/test-cdio-basic-tests-stderr-${{ env.PYTHON_VERSION }}.txt
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Store container log
 | 
					 | 
				
			||||||
        if: always()
 | 
					 | 
				
			||||||
        uses: actions/upload-artifact@v4
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          name: test-cdio-basic-tests-output-py${{ env.PYTHON_VERSION }}
 | 
					 | 
				
			||||||
          path: output-logs
 | 
					 | 
				
			||||||
@@ -2,10 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# @NOTE! I would love to move to 3.11 but it breaks the async handler in changedetectionio/content_fetchers/puppeteer.py
 | 
					# @NOTE! I would love to move to 3.11 but it breaks the async handler in changedetectionio/content_fetchers/puppeteer.py
 | 
				
			||||||
#        If you know how to fix it, please do! and test it for both 3.10 and 3.11
 | 
					#        If you know how to fix it, please do! and test it for both 3.10 and 3.11
 | 
				
			||||||
 | 
					FROM python:3.10-slim-bookworm as builder
 | 
				
			||||||
ARG PYTHON_VERSION=3.10
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
FROM python:${PYTHON_VERSION}-slim-bookworm as builder
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# See `cryptography` pin comment in requirements.txt
 | 
					# See `cryptography` pin comment in requirements.txt
 | 
				
			||||||
ARG CRYPTOGRAPHY_DONT_BUILD_RUST=1
 | 
					ARG CRYPTOGRAPHY_DONT_BUILD_RUST=1
 | 
				
			||||||
@@ -35,7 +32,7 @@ RUN pip install --target=/dependencies playwright~=1.41.2 \
 | 
				
			|||||||
    || echo "WARN: Failed to install Playwright. The application can still run, but the Playwright option will be disabled."
 | 
					    || echo "WARN: Failed to install Playwright. The application can still run, but the Playwright option will be disabled."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Final image stage
 | 
					# Final image stage
 | 
				
			||||||
FROM python:${PYTHON_VERSION}-slim-bookworm
 | 
					FROM python:3.10-slim-bookworm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
 | 
					RUN apt-get update && apt-get install -y --no-install-recommends \
 | 
				
			||||||
    libxslt1.1 \
 | 
					    libxslt1.1 \
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -339,7 +339,7 @@ def changedetection_app(config=None, datastore_o=None):
 | 
				
			|||||||
        # @todo needs a .itemsWithTag() or something - then we can use that in Jinaj2 and throw this away
 | 
					        # @todo needs a .itemsWithTag() or something - then we can use that in Jinaj2 and throw this away
 | 
				
			||||||
        for uuid, watch in datastore.data['watching'].items():
 | 
					        for uuid, watch in datastore.data['watching'].items():
 | 
				
			||||||
            # @todo tag notification_muted skip also (improve Watch model)
 | 
					            # @todo tag notification_muted skip also (improve Watch model)
 | 
				
			||||||
            if datastore.data['settings']['application'].get('rss_hide_muted_watches') and watch.get('notification_muted'):
 | 
					            if watch.get('notification_muted'):
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
            if limit_tag and not limit_tag in watch['tags']:
 | 
					            if limit_tag and not limit_tag in watch['tags']:
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
@@ -472,7 +472,7 @@ def changedetection_app(config=None, datastore_o=None):
 | 
				
			|||||||
                                 # Don't link to hosting when we're on the hosting environment
 | 
					                                 # Don't link to hosting when we're on the hosting environment
 | 
				
			||||||
                                 active_tag=active_tag,
 | 
					                                 active_tag=active_tag,
 | 
				
			||||||
                                 active_tag_uuid=active_tag_uuid,
 | 
					                                 active_tag_uuid=active_tag_uuid,
 | 
				
			||||||
                                 app_rss_token=datastore.data['settings']['application'].get('rss_access_token'),
 | 
					                                 app_rss_token=datastore.data['settings']['application']['rss_access_token'],
 | 
				
			||||||
                                 datastore=datastore,
 | 
					                                 datastore=datastore,
 | 
				
			||||||
                                 errored_count=errored_count,
 | 
					                                 errored_count=errored_count,
 | 
				
			||||||
                                 form=form,
 | 
					                                 form=form,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -572,8 +572,6 @@ class globalSettingsApplicationForm(commonSettingsForm):
 | 
				
			|||||||
    removepassword_button = SubmitField('Remove password', render_kw={"class": "pure-button pure-button-primary"})
 | 
					    removepassword_button = SubmitField('Remove password', render_kw={"class": "pure-button pure-button-primary"})
 | 
				
			||||||
    render_anchor_tag_content = BooleanField('Render anchor tag content', default=False)
 | 
					    render_anchor_tag_content = BooleanField('Render anchor tag content', default=False)
 | 
				
			||||||
    shared_diff_access = BooleanField('Allow access to view diff page when password is enabled', default=False, validators=[validators.Optional()])
 | 
					    shared_diff_access = BooleanField('Allow access to view diff page when password is enabled', default=False, validators=[validators.Optional()])
 | 
				
			||||||
    rss_hide_muted_watches = BooleanField('Hide muted watches from RSS feed', default=True,
 | 
					 | 
				
			||||||
                                      validators=[validators.Optional()])
 | 
					 | 
				
			||||||
    filter_failure_notification_threshold_attempts = IntegerField('Number of times the filter can be missing before sending a notification',
 | 
					    filter_failure_notification_threshold_attempts = IntegerField('Number of times the filter can be missing before sending a notification',
 | 
				
			||||||
                                                                  render_kw={"style": "width: 5em;"},
 | 
					                                                                  render_kw={"style": "width: 5em;"},
 | 
				
			||||||
                                                                  validators=[validators.NumberRange(min=0,
 | 
					                                                                  validators=[validators.NumberRange(min=0,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -46,8 +46,6 @@ class model(dict):
 | 
				
			|||||||
                    'pager_size': 50,
 | 
					                    'pager_size': 50,
 | 
				
			||||||
                    'password': False,
 | 
					                    'password': False,
 | 
				
			||||||
                    'render_anchor_tag_content': False,
 | 
					                    'render_anchor_tag_content': False,
 | 
				
			||||||
                    'rss_access_token': None,
 | 
					 | 
				
			||||||
                    'rss_hide_muted_watches': True,
 | 
					 | 
				
			||||||
                    'schema_version' : 0,
 | 
					                    'schema_version' : 0,
 | 
				
			||||||
                    'shared_diff_access': False,
 | 
					                    'shared_diff_access': False,
 | 
				
			||||||
                    'webdriver_delay': None , # Extra delay in seconds before extracting text
 | 
					                    'webdriver_delay': None , # Extra delay in seconds before extracting text
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1021,11 +1021,6 @@ ul {
 | 
				
			|||||||
  border-radius: 10px;
 | 
					  border-radius: 10px;
 | 
				
			||||||
  margin-bottom: 1em;
 | 
					  margin-bottom: 1em;
 | 
				
			||||||
  display: none;
 | 
					  display: none;
 | 
				
			||||||
  button {
 | 
					 | 
				
			||||||
    /* some space if they wrap the page */
 | 
					 | 
				
			||||||
    margin-bottom: 3px;
 | 
					 | 
				
			||||||
    margin-top: 3px;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.checkbox-uuid {
 | 
					.checkbox-uuid {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1127,10 +1127,6 @@ ul {
 | 
				
			|||||||
  border-radius: 10px;
 | 
					  border-radius: 10px;
 | 
				
			||||||
  margin-bottom: 1em;
 | 
					  margin-bottom: 1em;
 | 
				
			||||||
  display: none; }
 | 
					  display: none; }
 | 
				
			||||||
  #checkbox-operations button {
 | 
					 | 
				
			||||||
    /* some space if they wrap the page */
 | 
					 | 
				
			||||||
    margin-bottom: 3px;
 | 
					 | 
				
			||||||
    margin-top: 3px; }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
.checkbox-uuid > * {
 | 
					.checkbox-uuid > * {
 | 
				
			||||||
  vertical-align: middle; }
 | 
					  vertical-align: middle; }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -124,12 +124,12 @@ class ChangeDetectionStore:
 | 
				
			|||||||
                self.__data['app_guid'] = str(uuid_builder.uuid4())
 | 
					                self.__data['app_guid'] = str(uuid_builder.uuid4())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Generate the URL access token for RSS feeds
 | 
					        # Generate the URL access token for RSS feeds
 | 
				
			||||||
        if not self.__data['settings']['application'].get('rss_access_token'):
 | 
					        if not 'rss_access_token' in self.__data['settings']['application']:
 | 
				
			||||||
            secret = secrets.token_hex(16)
 | 
					            secret = secrets.token_hex(16)
 | 
				
			||||||
            self.__data['settings']['application']['rss_access_token'] = secret
 | 
					            self.__data['settings']['application']['rss_access_token'] = secret
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Generate the API access token
 | 
					        # Generate the API access token
 | 
				
			||||||
        if not self.__data['settings']['application'].get('api_access_token'):
 | 
					        if not 'api_access_token' in self.__data['settings']['application']:
 | 
				
			||||||
            secret = secrets.token_hex(16)
 | 
					            secret = secrets.token_hex(16)
 | 
				
			||||||
            self.__data['settings']['application']['api_access_token'] = secret
 | 
					            self.__data['settings']['application']['api_access_token'] = secret
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -62,9 +62,6 @@
 | 
				
			|||||||
                        <span class="pure-form-message-inline">Allow access to view watch diff page when password is enabled (Good for sharing the diff page)
 | 
					                        <span class="pure-form-message-inline">Allow access to view watch diff page when password is enabled (Good for sharing the diff page)
 | 
				
			||||||
                        </span>
 | 
					                        </span>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                    <div class="pure-control-group">
 | 
					 | 
				
			||||||
                        {{ render_checkbox_field(form.application.form.rss_hide_muted_watches) }}
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                    <div class="pure-control-group">
 | 
					                    <div class="pure-control-group">
 | 
				
			||||||
                        {{ render_field(form.application.form.pager_size) }}
 | 
					                        {{ render_field(form.application.form.pager_size) }}
 | 
				
			||||||
                        <span class="pure-form-message-inline">Number of items per page in the watch overview list, 0 to disable.</span>
 | 
					                        <span class="pure-form-message-inline">Number of items per page in the watch overview list, 0 to disable.</span>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,51 +1,42 @@
 | 
				
			|||||||
#!/usr/bin/python3
 | 
					#!/usr/bin/python3
 | 
				
			||||||
import asyncio
 | 
					import smtpd
 | 
				
			||||||
from aiosmtpd.controller import Controller
 | 
					import asyncore
 | 
				
			||||||
from aiosmtpd.smtp import SMTP
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Accept a SMTP message and offer a way to retrieve the last message via TCP Socket
 | 
					# Accept a SMTP message and offer a way to retrieve the last message via TCP Socket
 | 
				
			||||||
 | 
					
 | 
				
			||||||
last_received_message = b"Nothing"
 | 
					last_received_message = b"Nothing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CustomSMTPHandler:
 | 
					class CustomSMTPServer(smtpd.SMTPServer):
 | 
				
			||||||
    async def handle_DATA(self, server, session, envelope):
 | 
					
 | 
				
			||||||
 | 
					    def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
 | 
				
			||||||
        global last_received_message
 | 
					        global last_received_message
 | 
				
			||||||
        last_received_message = envelope.content
 | 
					        last_received_message = data
 | 
				
			||||||
        print('Receiving message from:', session.peer)
 | 
					        print('Receiving message from:', peer)
 | 
				
			||||||
        print('Message addressed from:', envelope.mail_from)
 | 
					        print('Message addressed from:', mailfrom)
 | 
				
			||||||
        print('Message addressed to  :', envelope.rcpt_tos)
 | 
					        print('Message addressed to  :', rcpttos)
 | 
				
			||||||
        print('Message length        :', len(envelope.content))
 | 
					        print('Message length        :', len(data))
 | 
				
			||||||
        print(envelope.content.decode('utf8'))
 | 
					        print(data.decode('utf8'))
 | 
				
			||||||
        return '250 Message accepted for delivery'
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class EchoServerProtocol(asyncio.Protocol):
 | 
					# Just print out the last message received on plain TCP socket server
 | 
				
			||||||
    def connection_made(self, transport):
 | 
					class EchoServer(asyncore.dispatcher):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, host, port):
 | 
				
			||||||
 | 
					        asyncore.dispatcher.__init__(self)
 | 
				
			||||||
 | 
					        self.create_socket()
 | 
				
			||||||
 | 
					        self.set_reuse_addr()
 | 
				
			||||||
 | 
					        self.bind((host, port))
 | 
				
			||||||
 | 
					        self.listen(5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def handle_accepted(self, sock, addr):
 | 
				
			||||||
        global last_received_message
 | 
					        global last_received_message
 | 
				
			||||||
        self.transport = transport
 | 
					        print('Incoming connection from %s' % repr(addr))
 | 
				
			||||||
        peername = transport.get_extra_info('peername')
 | 
					        sock.send(last_received_message)
 | 
				
			||||||
        print('Incoming connection from {}'.format(peername))
 | 
					 | 
				
			||||||
        self.transport.write(last_received_message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        last_received_message = b''
 | 
					        last_received_message = b''
 | 
				
			||||||
        self.transport.close()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def main():
 | 
					server = CustomSMTPServer(('0.0.0.0', 11025), None)  # SMTP mail goes here
 | 
				
			||||||
    # Start the SMTP server
 | 
					server2 = EchoServer('0.0.0.0', 11080)  # Echo back last message received
 | 
				
			||||||
    controller = Controller(CustomSMTPHandler(), hostname='0.0.0.0', port=11025)
 | 
					asyncore.loop()
 | 
				
			||||||
    controller.start()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Start the TCP Echo server
 | 
					 | 
				
			||||||
    loop = asyncio.get_running_loop()
 | 
					 | 
				
			||||||
    server = await loop.create_server(
 | 
					 | 
				
			||||||
        lambda: EchoServerProtocol(),
 | 
					 | 
				
			||||||
        '0.0.0.0', 11080
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    async with server:
 | 
					 | 
				
			||||||
        await server.serve_forever()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    asyncio.run(main())
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,8 +32,6 @@ def get_last_message_from_smtp_server():
 | 
				
			|||||||
    client_socket.connect((smtp_test_server, port))  # connect to the server
 | 
					    client_socket.connect((smtp_test_server, port))  # connect to the server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    data = client_socket.recv(50024).decode()  # receive response
 | 
					    data = client_socket.recv(50024).decode()  # receive response
 | 
				
			||||||
    logging.info("get_last_message_from_smtp_server..")
 | 
					 | 
				
			||||||
    logging.info(data)
 | 
					 | 
				
			||||||
    client_socket.close()  # close the connection
 | 
					    client_socket.close()  # close the connection
 | 
				
			||||||
    return data
 | 
					    return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -85,7 +83,7 @@ def test_check_notification_email_formats_default_HTML(client, live_server):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    # The email should have two bodies, and the text/html part should be <br>
 | 
					    # The email should have two bodies, and the text/html part should be <br>
 | 
				
			||||||
    assert 'Content-Type: text/plain' in msg
 | 
					    assert 'Content-Type: text/plain' in msg
 | 
				
			||||||
    assert '(added) So let\'s see what happens.\r\n' in msg  # The plaintext part with \r\n
 | 
					    assert '(added) So let\'s see what happens.\n' in msg  # The plaintext part with \n
 | 
				
			||||||
    assert 'Content-Type: text/html' in msg
 | 
					    assert 'Content-Type: text/html' in msg
 | 
				
			||||||
    assert '(added) So let\'s see what happens.<br>' in msg  # the html part
 | 
					    assert '(added) So let\'s see what happens.<br>' in msg  # the html part
 | 
				
			||||||
    res = client.get(url_for("form_delete", uuid="all"), follow_redirects=True)
 | 
					    res = client.get(url_for("form_delete", uuid="all"), follow_redirects=True)
 | 
				
			||||||
@@ -152,7 +150,7 @@ def test_check_notification_email_formats_default_Text_override_HTML(client, liv
 | 
				
			|||||||
    # The email should not have two bodies, should be TEXT only
 | 
					    # The email should not have two bodies, should be TEXT only
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    assert 'Content-Type: text/plain' in msg
 | 
					    assert 'Content-Type: text/plain' in msg
 | 
				
			||||||
    assert '(added) So let\'s see what happens.\r\n' in msg  # The plaintext part with \r\n
 | 
					    assert '(added) So let\'s see what happens.\n' in msg  # The plaintext part with \n
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    set_original_response()
 | 
					    set_original_response()
 | 
				
			||||||
    # Now override as HTML format
 | 
					    # Now override as HTML format
 | 
				
			||||||
@@ -173,7 +171,7 @@ def test_check_notification_email_formats_default_Text_override_HTML(client, liv
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    # The email should have two bodies, and the text/html part should be <br>
 | 
					    # The email should have two bodies, and the text/html part should be <br>
 | 
				
			||||||
    assert 'Content-Type: text/plain' in msg
 | 
					    assert 'Content-Type: text/plain' in msg
 | 
				
			||||||
    assert '(removed) So let\'s see what happens.\r\n' in msg  # The plaintext part with \n
 | 
					    assert '(removed) So let\'s see what happens.\n' in msg  # The plaintext part with \n
 | 
				
			||||||
    assert 'Content-Type: text/html' in msg
 | 
					    assert 'Content-Type: text/html' in msg
 | 
				
			||||||
    assert '(removed) So let\'s see what happens.<br>' in msg  # the html part
 | 
					    assert '(removed) So let\'s see what happens.<br>' in msg  # the html part
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,7 +29,9 @@ chardet>2.3.0
 | 
				
			|||||||
wtforms~=3.0
 | 
					wtforms~=3.0
 | 
				
			||||||
jsonpath-ng~=1.5.3
 | 
					jsonpath-ng~=1.5.3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dnspython==2.6.1
 | 
					# Pinned: module 'eventlet.green.select' has no attribute 'epoll'
 | 
				
			||||||
 | 
					# https://github.com/eventlet/eventlet/issues/805#issuecomment-1640463482
 | 
				
			||||||
 | 
					dnspython==2.3.0 # related to eventlet fixes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# jq not available on Windows so must be installed manually
 | 
					# jq not available on Windows so must be installed manually
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -84,5 +86,3 @@ pytest-flask ~=1.2
 | 
				
			|||||||
jsonschema==4.17.3
 | 
					jsonschema==4.17.3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
loguru
 | 
					loguru
 | 
				
			||||||
# Needed for > 3.10, https://github.com/microsoft/playwright-python/issues/2096
 | 
					 | 
				
			||||||
greenlet >= 3.0.3
 | 
					 | 
				
			||||||
		Reference in New Issue
	
	Block a user