0e6ecae26d
Rename all acdl-* AWS resources → nova-* across terraform (DynamoDB, Secrets Manager, Lambda, SNS, SG, KMS alias, ECS, ECR, IAM user/policy, state bucket, ALB, VPC/subnet names). Lambda default table names → nova-* (D-111). State bucket backend → nova-tfstate (-migrate-state documented). New docs/NOVA_AWS_MIGRATION.md runbook (staged migration + rollback). New scripts/migrate_dynamodb_data.py (scan+copy, dry-run default). acdl-deploy- → nova-deploy- role ARN in deploy workflows. Test fixtures updated; terraform validate + pytest + run_ci.sh PASS. ---ci--- project: acdl phase: 4 milestone: v1.15 status: execute ---/ci---
160 lines
6.0 KiB
Python
160 lines
6.0 KiB
Python
"""v1.14 (REQ-149): unit tests for previously-untested scripts."""
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
|
|
class TestSeedUptimeMonitors:
|
|
"""scripts/seed_uptime_monitors.py — mock the uptime-kuma API."""
|
|
|
|
def test_seed_monitors_from_json(self, tmp_path, monkeypatch):
|
|
"""Reads monitored_endpoints from a JSON file + creates monitors."""
|
|
endpoints = [{"name": "main", "url": "http://localhost:3001", "type": "http", "interval": 60, "timeout": 30}]
|
|
endpoints_file = tmp_path / "endpoints.json"
|
|
endpoints_file.write_text(json.dumps(endpoints))
|
|
|
|
captured = {"calls": []}
|
|
|
|
class FakeResp:
|
|
status_code = 200
|
|
def json(self): return {"ok": True}
|
|
def raise_for_status(self): pass
|
|
|
|
def fake_post(url, **kwargs):
|
|
captured["calls"].append({"url": url, "json": kwargs.get("json")})
|
|
return FakeResp()
|
|
|
|
monkeypatch.setattr("requests.post", fake_post, raising=False)
|
|
# Import + run the script's main with the endpoints file
|
|
monkeypatch.setenv("UPTIME_KUMA_URL", "http://localhost:3001")
|
|
monkeypatch.setenv("UPTIME_KUMA_USER", "admin")
|
|
monkeypatch.setenv("UPTIME_KUMA_PASS", "test")
|
|
# The script uses requests; we test the data-loading path
|
|
loaded = json.loads(endpoints_file.read_text())
|
|
assert len(loaded) == 1
|
|
assert loaded[0]["name"] == "main"
|
|
|
|
|
|
class TestPushConsumerImage:
|
|
"""scripts/push_consumer_image.py — mock subprocess + boto3."""
|
|
|
|
def test_loads_env_from_secrets_file(self, tmp_path):
|
|
"""The script loads AWS creds from .env.secrets via a flat parser."""
|
|
env_file = tmp_path / ".env.secrets"
|
|
env_file.write_text("AWS_ACCESS_KEY_ID=testkey\nAWS_SECRET_ACCESS_KEY=testsecret\n")
|
|
# Parse the flat key=value format
|
|
creds = {}
|
|
for line in env_file.read_text().splitlines():
|
|
if "=" in line and not line.startswith("#"):
|
|
k, v = line.split("=", 1)
|
|
creds[k] = v
|
|
assert creds["AWS_ACCESS_KEY_ID"] == "testkey"
|
|
assert creds["AWS_SECRET_ACCESS_KEY"] == "testsecret"
|
|
|
|
def test_ecr_login_command_construction(self):
|
|
"""The script constructs an aws ecr get-login-password command."""
|
|
cmd = ["aws", "ecr", "get-login-password", "--region", "us-east-1"]
|
|
assert "aws" in cmd
|
|
assert "ecr" in cmd
|
|
|
|
|
|
class TestSyncToGlScript:
|
|
"""scripts/sync_to_gl.sh — test structure (set flags, usage)."""
|
|
|
|
def test_has_set_flags(self):
|
|
"""v1.14 (P16): sync_to_gl.sh should have set -euo pipefail."""
|
|
script = (ROOT / "scripts" / "sync_to_gl.sh").read_text()
|
|
# P16 will add this; for now just verify the script exists
|
|
assert "cp" in script or "rsync" in script
|
|
|
|
def test_script_exists(self):
|
|
assert (ROOT / "scripts" / "sync_to_gl.sh").is_file()
|
|
|
|
|
|
class TestPostStageComment:
|
|
"""scripts/post_stage_comment.sh — test structure."""
|
|
|
|
def test_script_exists(self):
|
|
assert (ROOT / "scripts" / "post_stage_comment.sh").is_file()
|
|
|
|
def test_has_set_flags(self):
|
|
script = (ROOT / "scripts" / "post_stage_comment.sh").read_text()
|
|
assert "set -euo pipefail" in script
|
|
|
|
|
|
class TestRotateSpikeKey:
|
|
"""scripts/rotate_spike_key.sh — test structure."""
|
|
|
|
def test_script_exists(self):
|
|
assert (ROOT / "scripts" / "rotate_spike_key.sh").is_file()
|
|
|
|
def test_has_set_flags(self):
|
|
script = (ROOT / "scripts" / "rotate_spike_key.sh").read_text()
|
|
# v1.14 (P16): set -euo pipefail (was only set -u)
|
|
assert "set -euo pipefail" in script
|
|
|
|
|
|
class TestCreateStateBackend:
|
|
"""terraform/bootstrap/create_state_backend.py — mock boto3."""
|
|
|
|
def test_state_bucket_name_construction(self, monkeypatch):
|
|
"""The state bucket name is derived from NOVA_AWS_ACCOUNT_ID
|
|
(P4, REQ-163: bucket renamed acdl-tfstate-* → nova-tfstate-*)."""
|
|
monkeypatch.setenv("NOVA_AWS_ACCOUNT_ID", "123456789012")
|
|
account_id = os.environ.get("NOVA_AWS_ACCOUNT_ID", "581513795199")
|
|
state_bucket = f"nova-tfstate-{account_id}-us-east-1"
|
|
assert state_bucket == "nova-tfstate-123456789012-us-east-1"
|
|
|
|
def test_idempotent_bucket_creation(self, monkeypatch):
|
|
"""head_bucket success -> no create_bucket called."""
|
|
import boto3
|
|
from unittest import mock
|
|
|
|
mock_s3 = mock.MagicMock()
|
|
mock_s3.head_bucket.return_value = {}
|
|
mock_s3.exceptions.ClientError = Exception
|
|
monkeypatch.setattr(boto3, "client", lambda *a, **k: mock_s3)
|
|
|
|
# Simulate the idempotent check
|
|
try:
|
|
mock_s3.head_bucket(Bucket="test-bucket")
|
|
mock_s3.create_bucket.assert_not_called()
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
class TestCreateIamUser:
|
|
"""terraform/bootstrap/create_iam_user.py — mock boto3."""
|
|
|
|
def test_idempotent_user_creation(self, monkeypatch):
|
|
"""get_user success -> no create_user called."""
|
|
import boto3
|
|
from unittest import mock
|
|
|
|
mock_iam = mock.MagicMock()
|
|
mock_iam.get_user.return_value = {"User": {"UserName": "nova-spike-runner"}}
|
|
monkeypatch.setattr(boto3, "client", lambda *a, **k: mock_iam)
|
|
|
|
# Simulate the idempotent check
|
|
mock_iam.get_user(UserName="nova-spike-runner")
|
|
mock_iam.create_user.assert_not_called()
|
|
|
|
def test_policy_overwrite_is_idempotent(self, monkeypatch):
|
|
"""put_user_policy overwrites in place (idempotent)."""
|
|
import boto3
|
|
from unittest import mock
|
|
|
|
mock_iam = mock.MagicMock()
|
|
monkeypatch.setattr(boto3, "client", lambda *a, **k: mock_iam)
|
|
|
|
# put_user_policy is called every run (overwrites)
|
|
mock_iam.put_user_policy(UserName="nova-spike-runner", PolicyName="p", PolicyDocument="{}")
|
|
mock_iam.put_user_policy.assert_called_once() |