74 lines
5.2 KiB
Python
74 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Concurrent form writes, single-use tokens and corrupt-store preservation."""
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
import json
|
|
from pathlib import Path
|
|
import subprocess
|
|
import tempfile
|
|
import time
|
|
from support import copy_core, write_json
|
|
|
|
|
|
def php(root, code):
|
|
return subprocess.check_output(["php", "-r", code], cwd=root, text=True).strip()
|
|
|
|
|
|
with tempfile.TemporaryDirectory(prefix="raiju-storage-smoke-") as folder:
|
|
root = copy_core(folder)
|
|
prefix = "require 'includes/forms.php'; "
|
|
with ThreadPoolExecutor(max_workers=12) as workers:
|
|
tokens = list(workers.map(lambda _: php(root, prefix + "echo raijuCreateFormToken('review');"), range(24)))
|
|
token_path = root / "content/form-tokens.json"
|
|
stored = json.loads(token_path.read_text())
|
|
assert len(stored) == len(tokens), "Concurrent renders lost form tokens"
|
|
for entry in stored.values():
|
|
entry["created"] = int(time.time()) - 5
|
|
write_json(token_path, stored)
|
|
with ThreadPoolExecutor(max_workers=12) as workers:
|
|
results = list(workers.map(lambda _: php(root, prefix + "echo json_encode(raijuValidateFormToken('" + tokens[0] + "', 'review'));"), range(24)))
|
|
assert sum(json.loads(result)["valid"] for result in results) == 1, "Token accepted more than once"
|
|
with ThreadPoolExecutor(max_workers=12) as workers:
|
|
results = list(workers.map(lambda index: php(root, prefix + f"echo (int)raijuFormsSaveSubmission(['id'=>'mail-{index}']);"), range(24)))
|
|
assert all(result == "1" for result in results)
|
|
mail_path = root / "content/mails.json"
|
|
mails = json.loads(mail_path.read_text())
|
|
assert len({mail["id"] for mail in mails}) == 24, "Concurrent submissions lost messages"
|
|
with ThreadPoolExecutor(max_workers=12) as workers:
|
|
list(workers.map(lambda _: php(root, prefix + "raijuFormRecordAttempt('review-client', true);"), range(24)))
|
|
rates = json.loads((root / "content/form-rate-limit.json").read_text())
|
|
assert len(rates["review-client"]["submissions"]) == 24, "Concurrent attempts lost rate-limit entries"
|
|
with ThreadPoolExecutor(max_workers=12) as workers:
|
|
admissions = list(workers.map(lambda _: php(root, prefix + "echo (int)raijuFormReserveRequest('parallel-client');"), range(24)))
|
|
assert admissions.count("1") == 3, "Parallel requests bypassed the submission limit"
|
|
mail_path.write_text("{damaged-json")
|
|
assert php(root, prefix + "echo (int)raijuFormsSaveSubmission(['id'=>'must-not-overwrite']);") == "0"
|
|
assert mail_path.read_text() == "{damaged-json", "Damaged inbox overwritten"
|
|
# Fallback generation merges one missing field without overwriting concurrent edits.
|
|
write_json(root / 'content/pages/en_fallback.json', {'page': 'en_fallback', 'title': 'Keep this title'})
|
|
prefix = "require 'includes/content-loader.php'; "
|
|
with ThreadPoolExecutor(max_workers=12) as workers:
|
|
list(workers.map(lambda i: php(root, prefix + f"autoGenerateContentField('en_fallback','fields.key{i}','value{i}');"), range(24)))
|
|
php(root, prefix + "autoGenerateContentField('en_fallback','title','Wrong fallback');")
|
|
fallback = json.loads((root / 'content/pages/en_fallback.json').read_text())
|
|
assert fallback['title'] == 'Keep this title' and len(fallback['fields']) == 24
|
|
prefix = "require 'includes/ai/ai-helper.php'; "
|
|
with ThreadPoolExecutor(max_workers=12) as workers:
|
|
list(workers.map(lambda _: php(root, prefix + "raijuAiRecordUsage('text', 10, 2, 1);"), range(24)))
|
|
usage = json.loads((root / "content/ai-usage.json").read_text())
|
|
bucket = next(iter(usage["days"].values()))
|
|
assert bucket["requests"] == 24 and bucket["estimatedCostCents"] == 24, "Lost AI usage"
|
|
php(root, prefix + "raijuAiSaveImageJob(['id'=>'job_0123456789abcdef','status'=>'queued']);")
|
|
with ThreadPoolExecutor(max_workers=12) as workers:
|
|
claims = list(workers.map(lambda _: php(root, prefix + "$claimed=false; raijuAiMarkImageJobRunning('job_0123456789abcdef',$claimed); echo (int)$claimed;"), range(24)))
|
|
assert claims.count("1") == 1, "Multiple workers claimed the same paid image job"
|
|
with ThreadPoolExecutor(max_workers=12) as workers:
|
|
list(workers.map(lambda _: php(root, prefix + "raijuAiRecordImageHistory(['prompt'=>str_repeat('ä',3999).'€','status'=>'error']);"), range(24)))
|
|
history = json.loads((root / "content/ai-image-history.json").read_text())
|
|
assert len(history['items']) == 24 and all(item['prompt'].endswith('€') for item in history['items'])
|
|
prefix = "require 'includes/analytics-helper.php'; $_SERVER['REQUEST_URI']='/test'; $_SERVER['HTTP_USER_AGENT']='Mozilla/5.0'; "
|
|
with ThreadPoolExecutor(max_workers=12) as workers:
|
|
list(workers.map(lambda _: php(root, prefix + "raijuAnalyticsTrack('en_test','en','test');"), range(24)))
|
|
analytics = json.loads(subprocess.check_output(['php', '-r', "require 'includes/analytics-helper.php'; echo json_encode(raijuAnalyticsLoad());"], cwd=root, text=True))
|
|
assert next(iter(analytics['days'].values()))['views'] == 24, 'Concurrent page views were lost'
|
|
print("PASS concurrent tokens, submissions, rate counters, AI usage/history, single image worker, analytics and corrupt-store preservation")
|