diff --git a/t_sites/test_tempesta_tech.py b/t_sites/test_tempesta_tech.py index d0909c871..b1c959327 100644 --- a/t_sites/test_tempesta_tech.py +++ b/t_sites/test_tempesta_tech.py @@ -4,6 +4,9 @@ __copyright__ = "Copyright (C) 2024 Tempesta Technologies, Inc." __license__ = "GPL2" +import json +import re +import time import run_config from framework import tester @@ -54,10 +57,48 @@ class TestTempestaTechSite(NetfilterMarkMixin, tester.TempestaTest): " https://${tempesta_ip}" ), }, + { + "id": "get_nonce", + "type": "curl", + "load_cookies": True, + "http2": True, + "uri": "/wp-admin/admin-ajax.php?action=rest-nonce", + }, + { + "id": "get_admin", + "type": "curl", + "http2": True, + "load_cookies": True, + "uri": "/wp-admin/", + }, + { + "id": "blog_post", + "type": "curl", + "http2": True, + "load_cookies": True, + "uri": "/index.php?rest_route=/wp/v2/posts", + }, + { + "id": "post_form", + "type": "curl", + "http2": True, + "headers": { + "Content-Type": "application/x-www-form-urlencoded", + }, + }, + { + "id": "post_admin_form", + "type": "curl", + "http2": True, + "headers": { + "Content-Type": "application/x-www-form-urlencoded", + }, + }, ] tempesta = { "config": """ + listen 80 proto=http; listen 443 proto=h2; cache 2; @@ -65,7 +106,7 @@ class TestTempestaTechSite(NetfilterMarkMixin, tester.TempestaTest): cache_methods GET HEAD; cache_purge; # Allow purging from the containers (upstream), localhost (VM) and the host. - cache_purge_acl ${server_ip} 127.0.0.2; + cache_purge_acl ${client_ip}; access_log on; @@ -73,6 +114,7 @@ class TestTempestaTechSite(NetfilterMarkMixin, tester.TempestaTest): request_rate 200; http_method_override_allowed true; http_methods post put get purge; + http_strict_host_checking false; } block_action attack reply; @@ -114,7 +156,7 @@ class TestTempestaTechSite(NetfilterMarkMixin, tester.TempestaTest): """ } - def get_response(self, client: CurlClient) -> CurlResponse: + def send_request(self, client: CurlClient) -> CurlResponse: client.headers["Host"] = "tempesta-tech.com" client.start() self.wait_while_busy(client) @@ -132,7 +174,7 @@ def login(self, load_cookies=False): client.data = f"log={run_config.WEBSITE_USER}&pwd={run_config.WEBSITE_PASSWORD}" client.load_cookies = load_cookies - response = self.get_response(client) + response = self.send_request(client) self.assertEqual(response.status, 302) # Login page set multiple cookies self.assertGreater(len(response.multi_headers["set-cookie"]), 1) @@ -148,7 +190,7 @@ def purge_cache(self, uri, fetch=False): client = self.get_client("purge_cache") client.set_uri(uri) client.headers = {"X-Tempesta-Cache": "get"} if fetch else {} - response = self.get_response(client) + response = self.send_request(client) self.assertEqual(response.status, 200) def test_get_resource(self): @@ -171,7 +213,7 @@ def test_get_resource(self): ]: with self.subTest("GET", uri=uri): client.set_uri(uri) - response = self.get_response(client) + response = self.send_request(client) self.assertEqual(response.status, expected_code, response) self.assertFalse(response.stderr) length = response.headers.get("content-length") @@ -187,7 +229,7 @@ def test_page_cached(self): client.set_uri(uri) with self.subTest("First request, expect non-cached response"): - response = self.get_response(client) + response = self.send_request(client) self.assertEqual(response.status, 200) self.assertFalse( self.check_cached_headers(response.headers), @@ -195,7 +237,7 @@ def test_page_cached(self): ) with self.subTest("Second request, expect cached response"): - response = self.get_response(client) + response = self.send_request(client) self.assertEqual(response.status, 200) self.assertTrue( self.check_cached_headers(response.headers), @@ -204,7 +246,7 @@ def test_page_cached(self): with self.subTest("Third request, expect non-cached response after cache purge"): self.purge_cache(uri) - response = self.get_response(client) + response = self.send_request(client) self.assertEqual(response.status, 200) self.assertFalse( self.check_cached_headers(response.headers), @@ -232,7 +274,7 @@ def test_blog_post_cached(self): # and subsequent ones are from the cache for i, cached in enumerate([False, True, True], 1): with self.subTest("Get blog post", i=i, expect_cached=cached): - response = self.get_response(client) + response = self.send_request(client) self.assertEqual(response.status, 200) self.assertFalse(response.stderr) self.assertTrue(response.stdout.endswith("")) @@ -255,7 +297,7 @@ def test_blog_post_not_cached_for_authenticated_user(self): client.set_uri("/blog/cdn-non-hierarchical-caching/") for i in range(3): with self.subTest("Get blog post", i=i): - response = self.get_response(client) + response = self.send_request(client) self.assertEqual(response.status, 200) self.assertFalse(response.stderr) self.assertFalse(self.check_cached_headers(response.headers)) @@ -275,7 +317,7 @@ def test_get_resource_with_assets(self): ]: with self.subTest("GET", uri=uri): curl.set_uri(uri) - response = self.get_response(curl) + response = self.send_request(curl) self.assertEqual(response.status, 200, response) client.options = [cmd_args + uri] @@ -304,7 +346,7 @@ def test_get_admin_resource_with_assets(self): ]: with self.subTest("GET", uri=uri): curl.set_uri(uri) - response = self.get_response(curl) + response = self.send_request(curl) self.assertEqual(response.status, 200, response) # Construct command with the Cookie header nghttp.options = [f"{cmd_args}'{uri}' --header 'Cookie: {cookie}'"] @@ -313,3 +355,128 @@ def test_get_admin_resource_with_assets(self): nghttp.stop() self.assertNotIn("Some requests were not processed", nghttp.response_msg) self.assertFalse(nghttp.response_msg) + + def get_page_content(self, uri): + client = self.get_client("get") + client.set_uri(uri) + response = self.send_request(client) + self.assertEqual(response.status, 200) + self.assertFalse(response.stderr) + return response.stdout + + def get_index(self): + return self.get_page_content("/") + + def get_nonce(self): + client = self.get_client("get_nonce") + response = self.send_request(client) + self.assertEqual(response.status, 200) + nonce = response.stdout + self.assertTrue(nonce) + self.assertFalse(self.check_cached_headers(response.headers)) + return nonce + + def post_blog_post(self, title, nonce): + client = self.get_client("blog_post") + client.data = json.dumps( + { + "title": title, + "content": f"content for {title}", + "excerpt": "", + "status": "publish", + } + ) + client.headers = { + "Content-Type": "application/json", + "X-WP-Nonce": nonce, + } + response = self.send_request(client) + self.assertEqual(response.status, 201) + self.assertFalse(self.check_cached_headers(response.headers)) + post_id = response.headers["location"].split("/")[-1] + return post_id + + def get_comments_feed(self): + return self.get_page_content("/comments/feed/") + + def post_comment(self, post_id, text="Test", anonymous=True): + data = ( + f"comment={text}" + "&author=anonymous" + "&email=guest%40example.com" + "&url=" + "&submit=Post+Comment" + f"&comment_post_ID={post_id}" + "&comment_parent=0" + ) + response = self.post_form(uri="/wp-comments-post.php", data=data, anonymous=anonymous) + self.assertEqual(response.status, 302, response) + self.assertFalse(self.check_cached_headers(response.headers)) + + def post_form(self, uri, data, anonymous=True): + client = self.get_client("post_form" if anonymous else "post_admin_form") + client.load_cookies = not anonymous + client.set_uri(uri) + client.data = data + response = self.send_request(client) + self.assertFalse(self.check_cached_headers(response.headers)) + return response + + def get_post(self, post_title): + client = self.get_client("get") + client.set_uri(f"/uncategorized/{post_title}/") + response = self.send_request(client) + self.assertEqual(response.status, 200) + return response + + def test_blog_post_flow(self): + """ + - add a new post page; + - add a new comment to this page; + - update cache; + - cache the comment is present on this page; + """ + post_title = f"{time.time()}_post_title" + user_comment = f"{time.time()}_user_comment" + self.start_all_services(client=False) + + # Login + self.login() + + # Obtain nonce + nonce = self.get_nonce() + + # Publish new blog post + post_id = self.post_blog_post(title=post_title, nonce=nonce) + + # Get new blog post content + content = self.get_page_content(f"/uncategorized/{post_title}/") + self.assertIn(post_title, content, f"The {post_title} is not preset on blog post.") + + # No comments yet + self.assertNotIn(user_comment, content) + feed = self.get_comments_feed() + self.assertNotIn(user_comment, feed) + + # Post comment + self.post_comment(post_id, anonymous=False, text=user_comment) + response = self.get_post(post_title) + # Check comment is not present because response from cache + self.assertNotIn(user_comment, response.stdout) + self.assertNotIn(user_comment, self.get_comments_feed()) + + # Purge cache + self.purge_cache(f"/uncategorized/{post_title}/", fetch=True) + self.purge_cache("/comments/feed/", fetch=True) + + # Check comment present + self.assertIn( + user_comment, + self.get_post(post_title).stdout, + f"The {user_comment} is not present on post page.", + ) + self.assertIn( + user_comment, + self.get_comments_feed(), + f"The {user_comment} is not present on comments page.", + )