feat: add backup.retain_days config and local docker-compose for integration tests
- Add [backup] retain_days to config (default 1); --days becomes optional - Add scripts/docker-compose.yml (azurite) for local integration testing - Integration tests now use assets/local.toml and localhost endpoints - Update .gitignore and CLAUDE.md accordingly Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,3 +9,4 @@ backups/
|
|||||||
scan/
|
scan/
|
||||||
dist-source/
|
dist-source/
|
||||||
.worktrees/
|
.worktrees/
|
||||||
|
scripts/azurite/
|
||||||
|
|||||||
@@ -29,7 +29,10 @@ uv run pytest tests/ -v -m "not integration"
|
|||||||
# Run a single test file
|
# Run a single test file
|
||||||
uv run pytest tests/test_backup.py -v
|
uv run pytest tests/test_backup.py -v
|
||||||
|
|
||||||
# Run integration tests (requires Azure Storage emulator at 192.168.9.101)
|
# Start Azure Storage emulator (required for integration tests)
|
||||||
|
docker compose -f scripts/docker-compose.yml up -d
|
||||||
|
|
||||||
|
# Run integration tests (requires emulator running via docker compose above)
|
||||||
uv run pytest tests/test_integration.py -v
|
uv run pytest tests/test_integration.py -v
|
||||||
|
|
||||||
# Seed test data into the emulator
|
# Seed test data into the emulator
|
||||||
@@ -72,4 +75,4 @@ Override config path with `order-bak --config /path/to/config.toml <command>`.
|
|||||||
|
|
||||||
### Integration Tests
|
### Integration Tests
|
||||||
|
|
||||||
The integration test (`tests/test_integration.py`) hits a real Azure Storage emulator at `192.168.9.101:10002`. Override the endpoint via `ORDER_BAK_TEST_CONNECTION_STRING` env var. Tests are gated by the `integration` pytest marker.
|
Integration tests (`tests/test_integration.py`) require the Azure Storage emulator running locally via `scripts/docker-compose.yml` (azurite on `localhost:10002`). Start it with `docker compose -f scripts/docker-compose.yml up -d` before running. Tests are gated by the `integration` pytest marker. Override the endpoint via `ORDER_BAK_TEST_CONNECTION_STRING` env var if needed.
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ connection_string = ""
|
|||||||
# delay_ms = 200
|
# delay_ms = 200
|
||||||
# scan_dir = "./scan"
|
# scan_dir = "./scan"
|
||||||
|
|
||||||
|
[backup]
|
||||||
|
# retain_days = 1
|
||||||
|
|
||||||
[delete]
|
[delete]
|
||||||
# batch_size = 100
|
# batch_size = 100
|
||||||
# delay_ms = 300
|
# delay_ms = 300
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ page_size = 500
|
|||||||
delay_ms = 200
|
delay_ms = 200
|
||||||
scan_dir = "./scan"
|
scan_dir = "./scan"
|
||||||
|
|
||||||
|
[backup]
|
||||||
|
retain_days = 1
|
||||||
|
|
||||||
[delete]
|
[delete]
|
||||||
batch_size = 100
|
batch_size = 100
|
||||||
delay_ms = 300
|
delay_ms = 300
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ page_size = 500
|
|||||||
delay_ms = 200
|
delay_ms = 200
|
||||||
scan_dir = "./scan"
|
scan_dir = "./scan"
|
||||||
|
|
||||||
|
[backup]
|
||||||
|
retain_days = 1
|
||||||
|
|
||||||
[delete]
|
[delete]
|
||||||
batch_size = 100
|
batch_size = 100
|
||||||
delay_ms = 300
|
delay_ms = 300
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ page_size = 500
|
|||||||
delay_ms = 200
|
delay_ms = 200
|
||||||
scan_dir = "./scan"
|
scan_dir = "./scan"
|
||||||
|
|
||||||
|
[backup]
|
||||||
|
retain_days = 1
|
||||||
|
|
||||||
[delete]
|
[delete]
|
||||||
batch_size = 100
|
batch_size = 100
|
||||||
delay_ms = 300
|
delay_ms = 300
|
||||||
|
|||||||
9
scripts/docker-compose.yml
Normal file
9
scripts/docker-compose.yml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
services:
|
||||||
|
azure:
|
||||||
|
image: mcr.microsoft.com/azure-storage/azurite
|
||||||
|
ports:
|
||||||
|
- 10000:10000
|
||||||
|
- 10001:10001
|
||||||
|
- 10002:10002
|
||||||
|
volumes:
|
||||||
|
- ./azurite:/data
|
||||||
@@ -31,7 +31,7 @@ def scan(ctx, count):
|
|||||||
|
|
||||||
|
|
||||||
@main.command()
|
@main.command()
|
||||||
@click.option("--days", required=True, type=int, help="Backup orders older than N days (min 1)")
|
@click.option("--days", default=None, type=int, help="Backup orders older than N days (min 1); default from config backup.retain_days")
|
||||||
@click.option("--scan", "scan_path", default=None, type=click.Path(), help="Path to scan CSV (default: latest in scan_dir)")
|
@click.option("--scan", "scan_path", default=None, type=click.Path(), help="Path to scan CSV (default: latest in scan_dir)")
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def backup(ctx, days, scan_path):
|
def backup(ctx, days, scan_path):
|
||||||
@@ -42,6 +42,9 @@ def backup(ctx, days, scan_path):
|
|||||||
cfg = load_config(ctx.obj["config_path"])
|
cfg = load_config(ctx.obj["config_path"])
|
||||||
from order_bak.backup import backup_orders
|
from order_bak.backup import backup_orders
|
||||||
|
|
||||||
|
if days is None:
|
||||||
|
days = cfg.backup_retain_days
|
||||||
|
|
||||||
if scan_path:
|
if scan_path:
|
||||||
scan_file = Path(scan_path)
|
scan_file = Path(scan_path)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class Config:
|
|||||||
scan_dir: Path = Path("./scan")
|
scan_dir: Path = Path("./scan")
|
||||||
delete_batch_size: int = 100
|
delete_batch_size: int = 100
|
||||||
delete_delay_ms: int = 300
|
delete_delay_ms: int = 300
|
||||||
|
backup_retain_days: int = 1
|
||||||
|
|
||||||
|
|
||||||
def load_config(config_path: Path) -> Config:
|
def load_config(config_path: Path) -> Config:
|
||||||
@@ -30,6 +31,7 @@ def load_config(config_path: Path) -> Config:
|
|||||||
|
|
||||||
scan = raw.get("scan", {})
|
scan = raw.get("scan", {})
|
||||||
delete = raw.get("delete", {})
|
delete = raw.get("delete", {})
|
||||||
|
backup = raw.get("backup", {})
|
||||||
|
|
||||||
return Config(
|
return Config(
|
||||||
azure_connection_string=connection_string,
|
azure_connection_string=connection_string,
|
||||||
@@ -38,4 +40,5 @@ def load_config(config_path: Path) -> Config:
|
|||||||
scan_dir=Path(scan.get("scan_dir", "./scan")),
|
scan_dir=Path(scan.get("scan_dir", "./scan")),
|
||||||
delete_batch_size=delete.get("batch_size", 100),
|
delete_batch_size=delete.get("batch_size", 100),
|
||||||
delete_delay_ms=delete.get("delay_ms", 300),
|
delete_delay_ms=delete.get("delay_ms", 300),
|
||||||
|
backup_retain_days=backup.get("retain_days", 1),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from order_bak.csv_utils import read_csv
|
|||||||
from order_bak.delete import delete_orders
|
from order_bak.delete import delete_orders
|
||||||
from order_bak.scan import scan_orders
|
from order_bak.scan import scan_orders
|
||||||
|
|
||||||
CONFIG_PATH = Path(__file__).parent.parent / "assets" / "s101.toml"
|
CONFIG_PATH = Path(__file__).parent.parent / "assets" / "local.toml"
|
||||||
|
|
||||||
CONNECTION_STRING = os.environ.get(
|
CONNECTION_STRING = os.environ.get(
|
||||||
"ORDER_BAK_TEST_CONNECTION_STRING",
|
"ORDER_BAK_TEST_CONNECTION_STRING",
|
||||||
@@ -23,9 +23,9 @@ CONNECTION_STRING = os.environ.get(
|
|||||||
"AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/"
|
"AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/"
|
||||||
"K1SZFPTOtr/KBHBeksoGMGw==;"
|
"K1SZFPTOtr/KBHBeksoGMGw==;"
|
||||||
"DefaultEndpointsProtocol=http;"
|
"DefaultEndpointsProtocol=http;"
|
||||||
"BlobEndpoint=http://192.168.9.101:10000/devstoreaccount1;"
|
"BlobEndpoint=http://localhost:10000/devstoreaccount1;"
|
||||||
"QueueEndpoint=http://192.168.9.101:10001/devstoreaccount1;"
|
"QueueEndpoint=http://localhost:10001/devstoreaccount1;"
|
||||||
"TableEndpoint=http://192.168.9.101:10002/devstoreaccount1;"
|
"TableEndpoint=http://localhost:10002/devstoreaccount1;"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user