mirror of
				https://github.com/dgtlmoon/changedetection.io.git
				synced 2025-10-31 06:37:41 +00:00 
			
		
		
		
	Compare commits
	
		
			26 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 5ff9c8e112 | ||
|   | 62e99d41be | ||
|   | cdf07983db | ||
|   | 27401b3202 | ||
|   | 4baa9489c3 | ||
|   | 503e67f33e | ||
|   | f290d0e5fe | ||
|   | 933d4ce886 | ||
|   | 50c7e1bf8c | ||
|   | 73eb00a3e9 | ||
|   | e75561b2f5 | ||
|   | 005ed20741 | ||
|   | 2b0f851cbc | ||
|   | 56aeefc53e | ||
|   | f7c7a3dbb8 | ||
|   | e5775dd68e | ||
|   | 97e4ae1194 | ||
|   | de955a54bd | ||
|   | 5037e66ec1 | ||
|   | db48cc64f5 | ||
|   | bce02f9c82 | ||
|   | 76ffc3e891 | ||
|   | c6ee6687b5 | ||
|   | de48892243 | ||
|   | 6aded50aca | ||
|   | b8e279a025 | 
							
								
								
									
										228
									
								
								.github/workflows/test-only.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										228
									
								
								.github/workflows/test-only.yml
									
									
									
									
										vendored
									
									
								
							| @@ -4,17 +4,10 @@ name: ChangeDetection.io App Test | ||||
| on: [push, pull_request] | ||||
|  | ||||
| jobs: | ||||
|   test-application: | ||||
|   lint-code: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - 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 | ||||
|         run: | | ||||
|           pip3 install flake8 | ||||
| @@ -23,202 +16,31 @@ jobs: | ||||
|           # 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 | ||||
|  | ||||
|       - 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: 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-10: | ||||
|     needs: lint-code | ||||
|     uses: ./.github/workflows/test-stack-reusable-workflow.yml | ||||
|     with: | ||||
|       python-version: '3.10' | ||||
|  | ||||
|  | ||||
|       - 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' | ||||
|   test-application-3-11: | ||||
|     needs: lint-code | ||||
|     uses: ./.github/workflows/test-stack-reusable-workflow.yml | ||||
|     with: | ||||
|       python-version: '3.11' | ||||
|       skip-pypuppeteer: true | ||||
|  | ||||
|       - 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' | ||||
|   test-application-3-12: | ||||
|     needs: lint-code | ||||
|     uses: ./.github/workflows/test-stack-reusable-workflow.yml | ||||
|     with: | ||||
|       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 | ||||
|   test-application-3-13: | ||||
|     needs: lint-code | ||||
|     uses: ./.github/workflows/test-stack-reusable-workflow.yml | ||||
|     with: | ||||
|       python-version: '3.13' | ||||
|       skip-pypuppeteer: true | ||||
|        | ||||
							
								
								
									
										239
									
								
								.github/workflows/test-stack-reusable-workflow.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										239
									
								
								.github/workflows/test-stack-reusable-workflow.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,239 @@ | ||||
| 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,7 +2,10 @@ | ||||
|  | ||||
| # @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 | ||||
| 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 | ||||
| ARG CRYPTOGRAPHY_DONT_BUILD_RUST=1 | ||||
| @@ -32,7 +35,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." | ||||
|  | ||||
| # Final image stage | ||||
| FROM python:3.10-slim-bookworm | ||||
| FROM python:${PYTHON_VERSION}-slim-bookworm | ||||
|  | ||||
| RUN apt-get update && apt-get install -y --no-install-recommends \ | ||||
|     libxslt1.1 \ | ||||
|   | ||||
| @@ -28,7 +28,7 @@ def manage_user_agent(headers, current_ua=''): | ||||
|     :return: | ||||
|     """ | ||||
|     # Ask it what the user agent is, if its obviously ChromeHeadless, switch it to the default | ||||
|     ua_in_custom_headers = next((v for k, v in headers.items() if k.lower() == "user-agent"), None) | ||||
|     ua_in_custom_headers = headers.get('User-Agent') | ||||
|     if ua_in_custom_headers: | ||||
|         return ua_in_custom_headers | ||||
|  | ||||
|   | ||||
| @@ -115,12 +115,11 @@ class fetcher(Fetcher): | ||||
|  | ||||
|         # This user agent is similar to what was used when tweaking the evasions in inject_evasions_into_page(..) | ||||
|         user_agent = None | ||||
|         if request_headers: | ||||
|             user_agent = next((value for key, value in request_headers.items() if key.lower().strip() == 'user-agent'), None) | ||||
|             if user_agent: | ||||
|                 await self.page.setUserAgent(user_agent) | ||||
|                 # Remove it so it's not sent again with headers after | ||||
|                 [request_headers.pop(key) for key in list(request_headers) if key.lower().strip() == 'user-agent'.lower().strip()] | ||||
|         if request_headers and request_headers.get('User-Agent'): | ||||
|             # Request_headers should now be CaaseInsensitiveDict | ||||
|             # Remove it so it's not sent again with headers after | ||||
|             user_agent = request_headers.pop('User-Agent').strip() | ||||
|             await self.page.setUserAgent(user_agent) | ||||
|  | ||||
|         if not user_agent: | ||||
|             # Attempt to strip 'HeadlessChrome' etc | ||||
|   | ||||
| @@ -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 | ||||
|         for uuid, watch in datastore.data['watching'].items(): | ||||
|             # @todo tag notification_muted skip also (improve Watch model) | ||||
|             if watch.get('notification_muted'): | ||||
|             if datastore.data['settings']['application'].get('rss_hide_muted_watches') and watch.get('notification_muted'): | ||||
|                 continue | ||||
|             if limit_tag and not limit_tag in watch['tags']: | ||||
|                 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 | ||||
|                                  active_tag=active_tag, | ||||
|                                  active_tag_uuid=active_tag_uuid, | ||||
|                                  app_rss_token=datastore.data['settings']['application']['rss_access_token'], | ||||
|                                  app_rss_token=datastore.data['settings']['application'].get('rss_access_token'), | ||||
|                                  datastore=datastore, | ||||
|                                  errored_count=errored_count, | ||||
|                                  form=form, | ||||
|   | ||||
| @@ -572,6 +572,8 @@ class globalSettingsApplicationForm(commonSettingsForm): | ||||
|     removepassword_button = SubmitField('Remove password', render_kw={"class": "pure-button pure-button-primary"}) | ||||
|     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()]) | ||||
|     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', | ||||
|                                                                   render_kw={"style": "width: 5em;"}, | ||||
|                                                                   validators=[validators.NumberRange(min=0, | ||||
|   | ||||
| @@ -46,6 +46,8 @@ class model(dict): | ||||
|                     'pager_size': 50, | ||||
|                     'password': False, | ||||
|                     'render_anchor_tag_content': False, | ||||
|                     'rss_access_token': None, | ||||
|                     'rss_hide_muted_watches': True, | ||||
|                     'schema_version' : 0, | ||||
|                     'shared_diff_access': False, | ||||
|                     'webdriver_delay': None , # Extra delay in seconds before extracting text | ||||
|   | ||||
| @@ -333,7 +333,9 @@ class model(dict): | ||||
|         # Small hack so that we sleep just enough to allow 1 second  between history snapshots | ||||
|         # this is because history.txt indexes/keys snapshots by epoch seconds and we dont want dupe keys | ||||
|         if self.__newest_history_key and int(timestamp) == int(self.__newest_history_key): | ||||
|             time.sleep(timestamp - self.__newest_history_key) | ||||
|             logger.warning(f"Timestamp {timestamp} already exists, waiting 1 seconds so we have a unique key in history.txt") | ||||
|             timestamp = str(int(timestamp) + 1) | ||||
|             time.sleep(1) | ||||
|  | ||||
|         threshold = int(os.getenv('SNAPSHOT_BROTLI_COMPRESSION_THRESHOLD', 1024)) | ||||
|         skip_brotli = strtobool(os.getenv('DISABLE_BROTLI_TEXT_SNAPSHOT', 'False')) | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| from abc import abstractmethod | ||||
| import os | ||||
| import hashlib | ||||
| import re | ||||
| from copy import deepcopy | ||||
| from changedetectionio.strtobool import strtobool | ||||
| from copy import deepcopy | ||||
| from loguru import logger | ||||
| import hashlib | ||||
| import os | ||||
| import re | ||||
|  | ||||
| class difference_detection_processor(): | ||||
|  | ||||
| @@ -21,7 +21,7 @@ class difference_detection_processor(): | ||||
|         self.watch = deepcopy(self.datastore.data['watching'].get(watch_uuid)) | ||||
|  | ||||
|     def call_browser(self): | ||||
|  | ||||
|         from requests.structures import CaseInsensitiveDict | ||||
|         # Protect against file:// access | ||||
|         if re.search(r'^file://', self.watch.get('url', '').strip(), re.IGNORECASE): | ||||
|             if not strtobool(os.getenv('ALLOW_FILE_URI', 'false')): | ||||
| @@ -93,14 +93,16 @@ class difference_detection_processor(): | ||||
|             self.fetcher.browser_steps_screenshot_path = os.path.join(self.datastore.datastore_path, self.watch.get('uuid')) | ||||
|  | ||||
|         # Tweak the base config with the per-watch ones | ||||
|         request_headers = self.watch.get('headers', []) | ||||
|         request_headers.update(self.datastore.get_all_base_headers()) | ||||
|         request_headers.update(self.datastore.get_all_headers_in_textfile_for_watch(uuid=self.watch.get('uuid'))) | ||||
|         request_headers = CaseInsensitiveDict() | ||||
|  | ||||
|         ua = self.datastore.data['settings']['requests'].get('default_ua') | ||||
|         if ua and ua.get(prefer_fetch_backend): | ||||
|             request_headers.update({'User-Agent': ua.get(prefer_fetch_backend)}) | ||||
|  | ||||
|         request_headers.update(self.watch.get('headers', {})) | ||||
|         request_headers.update(self.datastore.get_all_base_headers()) | ||||
|         request_headers.update(self.datastore.get_all_headers_in_textfile_for_watch(uuid=self.watch.get('uuid'))) | ||||
|  | ||||
|         # https://github.com/psf/requests/issues/4525 | ||||
|         # Requests doesnt yet support brotli encoding, so don't put 'br' here, be totally sure that the user cannot | ||||
|         # do this by accident. | ||||
|   | ||||
| @@ -1021,6 +1021,11 @@ ul { | ||||
|   border-radius: 10px; | ||||
|   margin-bottom: 1em; | ||||
|   display: none; | ||||
|   button { | ||||
|     /* some space if they wrap the page */ | ||||
|     margin-bottom: 3px; | ||||
|     margin-top: 3px; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .checkbox-uuid { | ||||
|   | ||||
| @@ -1127,6 +1127,10 @@ ul { | ||||
|   border-radius: 10px; | ||||
|   margin-bottom: 1em; | ||||
|   display: none; } | ||||
|   #checkbox-operations button { | ||||
|     /* some space if they wrap the page */ | ||||
|     margin-bottom: 3px; | ||||
|     margin-top: 3px; } | ||||
|  | ||||
| .checkbox-uuid > * { | ||||
|   vertical-align: middle; } | ||||
|   | ||||
| @@ -124,12 +124,12 @@ class ChangeDetectionStore: | ||||
|                 self.__data['app_guid'] = str(uuid_builder.uuid4()) | ||||
|  | ||||
|         # Generate the URL access token for RSS feeds | ||||
|         if not 'rss_access_token' in self.__data['settings']['application']: | ||||
|         if not self.__data['settings']['application'].get('rss_access_token'): | ||||
|             secret = secrets.token_hex(16) | ||||
|             self.__data['settings']['application']['rss_access_token'] = secret | ||||
|  | ||||
|         # Generate the API access token | ||||
|         if not 'api_access_token' in self.__data['settings']['application']: | ||||
|         if not self.__data['settings']['application'].get('api_access_token'): | ||||
|             secret = secrets.token_hex(16) | ||||
|             self.__data['settings']['application']['api_access_token'] = secret | ||||
|  | ||||
| @@ -178,7 +178,7 @@ class ChangeDetectionStore: | ||||
|     @property | ||||
|     def has_unviewed(self): | ||||
|         for uuid, watch in self.__data['watching'].items(): | ||||
|             if watch.viewed == False: | ||||
|             if watch.history_n >= 2 and watch.viewed == False: | ||||
|                 return True | ||||
|         return False | ||||
|  | ||||
|   | ||||
| @@ -62,6 +62,9 @@ | ||||
|                         <span class="pure-form-message-inline">Allow access to view watch diff page when password is enabled (Good for sharing the diff page) | ||||
|                         </span> | ||||
|                     </div> | ||||
|                     <div class="pure-control-group"> | ||||
|                         {{ render_checkbox_field(form.application.form.rss_hide_muted_watches) }} | ||||
|                     </div> | ||||
|                     <div class="pure-control-group"> | ||||
|                         {{ 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> | ||||
|   | ||||
| @@ -1,42 +1,51 @@ | ||||
| #!/usr/bin/python3 | ||||
| import smtpd | ||||
| import asyncore | ||||
| import asyncio | ||||
| from aiosmtpd.controller import Controller | ||||
| from aiosmtpd.smtp import SMTP | ||||
|  | ||||
| # Accept a SMTP message and offer a way to retrieve the last message via TCP Socket | ||||
|  | ||||
| last_received_message = b"Nothing" | ||||
|  | ||||
|  | ||||
| class CustomSMTPServer(smtpd.SMTPServer): | ||||
|  | ||||
|     def process_message(self, peer, mailfrom, rcpttos, data, **kwargs): | ||||
| class CustomSMTPHandler: | ||||
|     async def handle_DATA(self, server, session, envelope): | ||||
|         global last_received_message | ||||
|         last_received_message = data | ||||
|         print('Receiving message from:', peer) | ||||
|         print('Message addressed from:', mailfrom) | ||||
|         print('Message addressed to  :', rcpttos) | ||||
|         print('Message length        :', len(data)) | ||||
|         print(data.decode('utf8')) | ||||
|         return | ||||
|         last_received_message = envelope.content | ||||
|         print('Receiving message from:', session.peer) | ||||
|         print('Message addressed from:', envelope.mail_from) | ||||
|         print('Message addressed to  :', envelope.rcpt_tos) | ||||
|         print('Message length        :', len(envelope.content)) | ||||
|         print(envelope.content.decode('utf8')) | ||||
|         return '250 Message accepted for delivery' | ||||
|  | ||||
|  | ||||
| # Just print out the last message received on plain TCP socket server | ||||
| 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): | ||||
| class EchoServerProtocol(asyncio.Protocol): | ||||
|     def connection_made(self, transport): | ||||
|         global last_received_message | ||||
|         print('Incoming connection from %s' % repr(addr)) | ||||
|         sock.send(last_received_message) | ||||
|         self.transport = transport | ||||
|         peername = transport.get_extra_info('peername') | ||||
|         print('Incoming connection from {}'.format(peername)) | ||||
|         self.transport.write(last_received_message) | ||||
|  | ||||
|         last_received_message = b'' | ||||
|         self.transport.close() | ||||
|  | ||||
|  | ||||
| server = CustomSMTPServer(('0.0.0.0', 11025), None)  # SMTP mail goes here | ||||
| server2 = EchoServer('0.0.0.0', 11080)  # Echo back last message received | ||||
| asyncore.loop() | ||||
| async def main(): | ||||
|     # Start the SMTP server | ||||
|     controller = Controller(CustomSMTPHandler(), hostname='0.0.0.0', port=11025) | ||||
|     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,6 +32,8 @@ def get_last_message_from_smtp_server(): | ||||
|     client_socket.connect((smtp_test_server, port))  # connect to the server | ||||
|  | ||||
|     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 | ||||
|     return data | ||||
|  | ||||
| @@ -71,6 +73,8 @@ def test_check_notification_email_formats_default_HTML(client, live_server): | ||||
|  | ||||
|     wait_for_all_checks(client) | ||||
|     set_longer_modified_response() | ||||
|     time.sleep(2) | ||||
|  | ||||
|     client.get(url_for("form_watch_checknow"), follow_redirects=True) | ||||
|     wait_for_all_checks(client) | ||||
|  | ||||
| @@ -81,7 +85,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> | ||||
|     assert 'Content-Type: text/plain' in msg | ||||
|     assert '(added) So let\'s see what happens.\n' in msg  # The plaintext part with \n | ||||
|     assert '(added) So let\'s see what happens.\r\n' in msg  # The plaintext part with \r\n | ||||
|     assert 'Content-Type: text/html' in msg | ||||
|     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) | ||||
| @@ -135,6 +139,7 @@ def test_check_notification_email_formats_default_Text_override_HTML(client, liv | ||||
|  | ||||
|     wait_for_all_checks(client) | ||||
|     set_longer_modified_response() | ||||
|     time.sleep(2) | ||||
|     client.get(url_for("form_watch_checknow"), follow_redirects=True) | ||||
|     wait_for_all_checks(client) | ||||
|  | ||||
| @@ -147,7 +152,7 @@ def test_check_notification_email_formats_default_Text_override_HTML(client, liv | ||||
|     # The email should not have two bodies, should be TEXT only | ||||
|  | ||||
|     assert 'Content-Type: text/plain' in msg | ||||
|     assert '(added) So let\'s see what happens.\n' in msg  # The plaintext part with \n | ||||
|     assert '(added) So let\'s see what happens.\r\n' in msg  # The plaintext part with \r\n | ||||
|  | ||||
|     set_original_response() | ||||
|     # Now override as HTML format | ||||
| @@ -168,7 +173,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> | ||||
|     assert 'Content-Type: text/plain' in msg | ||||
|     assert '(removed) So let\'s see what happens.\n' in msg  # The plaintext part with \n | ||||
|     assert '(removed) So let\'s see what happens.\r\n' in msg  # The plaintext part with \n | ||||
|     assert 'Content-Type: text/html' in msg | ||||
|     assert '(removed) So let\'s see what happens.<br>' in msg  # the html part | ||||
|  | ||||
|   | ||||
| @@ -105,6 +105,7 @@ def test_check_ldjson_price_autodetect(client, live_server): | ||||
|     wait_for_all_checks(client) | ||||
|  | ||||
|     # Trigger a check | ||||
|     time.sleep(1) | ||||
|     client.get(url_for("form_watch_checknow"), follow_redirects=True) | ||||
|     wait_for_all_checks(client) | ||||
|     # Offer should be gone | ||||
|   | ||||
| @@ -135,6 +135,9 @@ def test_check_basic_change_detection_functionality(client, live_server): | ||||
|     # It should have picked up the <title> | ||||
|     assert b'head title' in res.data | ||||
|  | ||||
|     # Be sure the last_viewed is going to be greater than the last snapshot | ||||
|     time.sleep(1) | ||||
|  | ||||
|     # hit the mark all viewed link | ||||
|     res = client.get(url_for("mark_all_viewed"), follow_redirects=True) | ||||
|  | ||||
|   | ||||
| @@ -9,9 +9,6 @@ def test_check_notification_error_handling(client, live_server): | ||||
|     live_server_setup(live_server) | ||||
|     set_original_response() | ||||
|  | ||||
|     # Give the endpoint time to spin up | ||||
|     time.sleep(1) | ||||
|  | ||||
|     # Set a URL and fetch it, then set a notification URL which is going to give errors | ||||
|     test_url = url_for('test_endpoint', _external=True) | ||||
|     res = client.post( | ||||
|   | ||||
| @@ -253,6 +253,62 @@ def test_method_in_request(client, live_server): | ||||
|     res = client.get(url_for("form_delete", uuid="all"), follow_redirects=True) | ||||
|     assert b'Deleted' in res.data | ||||
|  | ||||
| # Re #2408 - user-agent override test, also should handle case-insensitive header deduplication | ||||
| def test_ua_global_override(client, live_server): | ||||
|     # live_server_setup(live_server) | ||||
|     test_url = url_for('test_headers', _external=True) | ||||
|  | ||||
|     res = client.post( | ||||
|         url_for("settings_page"), | ||||
|         data={ | ||||
|             "application-fetch_backend": "html_requests", | ||||
|             "application-minutes_between_check": 180, | ||||
|             "requests-default_ua-html_requests": "html-requests-user-agent" | ||||
|         }, | ||||
|         follow_redirects=True | ||||
|     ) | ||||
|     assert b'Settings updated' in res.data | ||||
|  | ||||
|     res = client.post( | ||||
|         url_for("import_page"), | ||||
|         data={"urls": test_url}, | ||||
|         follow_redirects=True | ||||
|     ) | ||||
|     assert b"1 Imported" in res.data | ||||
|  | ||||
|     wait_for_all_checks(client) | ||||
|     res = client.get( | ||||
|         url_for("preview_page", uuid="first"), | ||||
|         follow_redirects=True | ||||
|     ) | ||||
|  | ||||
|     assert b"html-requests-user-agent" in res.data | ||||
|     # default user-agent should have shown by now | ||||
|     # now add a custom one in the headers | ||||
|  | ||||
|  | ||||
|     # Add some headers to a request | ||||
|     res = client.post( | ||||
|         url_for("edit_page", uuid="first"), | ||||
|         data={ | ||||
|             "url": test_url, | ||||
|             "tags": "testtag", | ||||
|             "fetch_backend": 'html_requests', | ||||
|             # Important - also test case-insensitive | ||||
|             "headers": "User-AGent: agent-from-watch"}, | ||||
|         follow_redirects=True | ||||
|     ) | ||||
|     assert b"Updated watch." in res.data | ||||
|     wait_for_all_checks(client) | ||||
|     res = client.get( | ||||
|         url_for("preview_page", uuid="first"), | ||||
|         follow_redirects=True | ||||
|     ) | ||||
|     assert b"agent-from-watch" in res.data | ||||
|     assert b"html-requests-user-agent" not in res.data | ||||
|     res = client.get(url_for("form_delete", uuid="all"), follow_redirects=True) | ||||
|     assert b'Deleted' in res.data | ||||
|  | ||||
| def test_headers_textfile_in_request(client, live_server): | ||||
|     #live_server_setup(live_server) | ||||
|     # Add our URL to the import page | ||||
| @@ -333,7 +389,7 @@ def test_headers_textfile_in_request(client, live_server): | ||||
|     # Not needed anymore | ||||
|     os.unlink('test-datastore/headers.txt') | ||||
|     os.unlink('test-datastore/headers-testtag.txt') | ||||
|     os.unlink('test-datastore/' + extract_UUID_from_client(client) + '/headers.txt') | ||||
|  | ||||
|     # The service should echo back the request verb | ||||
|     res = client.get( | ||||
|         url_for("preview_page", uuid="first"), | ||||
|   | ||||
| @@ -29,9 +29,7 @@ chardet>2.3.0 | ||||
| wtforms~=3.0 | ||||
| jsonpath-ng~=1.5.3 | ||||
|  | ||||
| # 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 | ||||
| dnspython==2.6.1 | ||||
|  | ||||
| # jq not available on Windows so must be installed manually | ||||
|  | ||||
| @@ -86,3 +84,5 @@ pytest-flask ~=1.2 | ||||
| jsonschema==4.17.3 | ||||
|  | ||||
| loguru | ||||
| # Needed for > 3.10, https://github.com/microsoft/playwright-python/issues/2096 | ||||
| greenlet >= 3.0.3 | ||||
		Reference in New Issue
	
	Block a user