Story 4.4 - simple check works
parent
b3e35b0b4d
commit
77356c4191
|
|
@ -0,0 +1,105 @@
|
||||||
|
# Story 4.4: Post-Deployment Verification - Implementation Summary
|
||||||
|
|
||||||
|
## Status: COMPLETE
|
||||||
|
|
||||||
|
Story points: 5
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Implemented a simple CLI command to verify deployed URLs return 200 OK status.
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### New CLI Command
|
||||||
|
Added `verify-deployment` command to `src/cli/commands.py`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run python main.py verify-deployment --batch-id <id> [--sample N] [--timeout 10]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Options:**
|
||||||
|
- `--batch-id, -b`: Project/batch ID to verify (required)
|
||||||
|
- `--sample, -s`: Number of random URLs to check (optional, default: check all)
|
||||||
|
- `--timeout, -t`: Request timeout in seconds (default: 10)
|
||||||
|
|
||||||
|
### Core Functionality
|
||||||
|
1. Queries database for deployed articles in specified batch
|
||||||
|
2. Filters articles with `deployed_url` and status='deployed'
|
||||||
|
3. Makes HTTP GET requests to verify 200 OK status
|
||||||
|
4. Supports checking all URLs or random sample
|
||||||
|
5. Clear output showing success/failure for each URL
|
||||||
|
6. Summary report with total checked, successful, and failed counts
|
||||||
|
|
||||||
|
### Code Changes
|
||||||
|
- **Modified:** `src/cli/commands.py`
|
||||||
|
- Added imports: `requests`, `random`
|
||||||
|
- Added `verify_deployment()` command function
|
||||||
|
|
||||||
|
### Acceptance Criteria Verification
|
||||||
|
- ✅ CLI command available: `verify-deployment --batch_id <id>`
|
||||||
|
- ✅ Takes batch ID as input
|
||||||
|
- ✅ Retrieves URLs for all articles in batch from database
|
||||||
|
- ✅ Makes HTTP GET requests to sample or all URLs
|
||||||
|
- ✅ Reports which URLs return 200 OK and which do not
|
||||||
|
- ✅ Clear, easy-to-read output
|
||||||
|
- ✅ Can be run manually after deployment
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Verify all URLs in a batch:
|
||||||
|
```bash
|
||||||
|
uv run python main.py verify-deployment --batch-id 10
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verify random sample of 5 URLs:
|
||||||
|
```bash
|
||||||
|
uv run python main.py verify-deployment --batch-id 10 --sample 5
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom timeout:
|
||||||
|
```bash
|
||||||
|
uv run python main.py verify-deployment --batch-id 10 --timeout 30
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sample Output
|
||||||
|
```
|
||||||
|
Verifying deployment for batch: My Project (ID: 10)
|
||||||
|
Keyword: main keyword
|
||||||
|
|
||||||
|
Found 25 deployed articles
|
||||||
|
Checking all 25 URLs
|
||||||
|
|
||||||
|
✓ https://example.com/article-1.html
|
||||||
|
✓ https://example.com/article-2.html
|
||||||
|
✗ https://example.com/article-3.html (HTTP 404)
|
||||||
|
...
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
Verification Summary
|
||||||
|
======================================================================
|
||||||
|
Total checked: 25
|
||||||
|
Successful: 24
|
||||||
|
Failed: 1
|
||||||
|
|
||||||
|
Failed URLs:
|
||||||
|
https://example.com/article-3.html
|
||||||
|
Title: Article Three Title
|
||||||
|
Error: 404
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
- `requests==2.32.5` (already in requirements.txt)
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
- Command help output verified
|
||||||
|
- Follows existing CLI command patterns
|
||||||
|
- Simple, focused implementation per user requirements
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- No authentication required (read-only operation)
|
||||||
|
- Uses existing database repositories
|
||||||
|
- Minimal dependencies
|
||||||
|
- No freelance features added - strictly adheres to acceptance criteria
|
||||||
|
- Can be integrated into auto-deploy workflow in future if needed
|
||||||
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
# Story 4.4: Post-Deployment Verification - Quick Start
|
||||||
|
|
||||||
|
## Command Usage
|
||||||
|
|
||||||
|
Verify that deployed URLs are live and returning 200 OK status:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run python main.py verify-deployment --batch-id <id>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
| Option | Short | Description | Default |
|
||||||
|
|--------|-------|-------------|---------|
|
||||||
|
| `--batch-id` | `-b` | Project/batch ID to verify | Required |
|
||||||
|
| `--sample` | `-s` | Number of random URLs to check | All |
|
||||||
|
| `--timeout` | `-t` | Request timeout in seconds | 10 |
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Check all URLs in a batch
|
||||||
|
```bash
|
||||||
|
uv run python main.py verify-deployment --batch-id 10
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check random sample of 5 URLs
|
||||||
|
```bash
|
||||||
|
uv run python main.py verify-deployment --batch-id 10 --sample 5
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use custom timeout
|
||||||
|
```bash
|
||||||
|
uv run python main.py verify-deployment --batch-id 10 --timeout 30
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
The command will:
|
||||||
|
1. Show batch information
|
||||||
|
2. Display each URL check result with ✓ (success) or ✗ (failed)
|
||||||
|
3. Provide a summary of results
|
||||||
|
4. List failed URLs with error details
|
||||||
|
|
||||||
|
## Exit Codes
|
||||||
|
|
||||||
|
- `0`: All URLs returned 200 OK
|
||||||
|
- `1`: One or more URLs failed or error occurred
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Only checks articles with `deployed_url` set and status='deployed'
|
||||||
|
- Follows redirects automatically
|
||||||
|
- No authentication required (read-only operation)
|
||||||
|
- Can be run multiple times safely
|
||||||
|
|
||||||
|
|
@ -7,7 +7,7 @@ To deploy all finalized HTML content (articles and boilerplate pages) for a batc
|
||||||
- **Story 4.1**: ✅ COMPLETE (22 story points, real-world validated)
|
- **Story 4.1**: ✅ COMPLETE (22 story points, real-world validated)
|
||||||
- **Story 4.2**: ✅ COMPLETE (implemented in Story 4.1)
|
- **Story 4.2**: ✅ COMPLETE (implemented in Story 4.1)
|
||||||
- **Story 4.3**: ✅ COMPLETE (implemented in Story 4.1)
|
- **Story 4.3**: ✅ COMPLETE (implemented in Story 4.1)
|
||||||
- **Story 4.4**: Not started
|
- **Story 4.4**: ✅ COMPLETE (5 story points)
|
||||||
- **Story 4.5**: Not started
|
- **Story 4.5**: Not started
|
||||||
|
|
||||||
## Stories
|
## Stories
|
||||||
|
|
@ -64,7 +64,7 @@ To deploy all finalized HTML content (articles and boilerplate pages) for a batc
|
||||||
**Note:** The `last_deployed_at` timestamp for `site_deployments` could be added as enhancement if needed.
|
**Note:** The `last_deployed_at` timestamp for `site_deployments` could be added as enhancement if needed.
|
||||||
|
|
||||||
### Story 4.4: Post-Deployment Verification
|
### Story 4.4: Post-Deployment Verification
|
||||||
**Status:** Not Started
|
**Status:** ✅ COMPLETE (5 story points)
|
||||||
|
|
||||||
**As a user**, I want a simple way to verify that a batch of articles has been deployed successfully, by checking a sample of URLs for a `200 OK` status, so I can have confidence the deployment worked.
|
**As a user**, I want a simple way to verify that a batch of articles has been deployed successfully, by checking a sample of URLs for a `200 OK` status, so I can have confidence the deployment worked.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ from src.deployment.bunny_storage import BunnyStorageClient, BunnyStorageError
|
||||||
from src.deployment.deployment_service import DeploymentService
|
from src.deployment.deployment_service import DeploymentService
|
||||||
from src.deployment.url_logger import URLLogger
|
from src.deployment.url_logger import URLLogger
|
||||||
import os
|
import os
|
||||||
|
import requests
|
||||||
|
import random
|
||||||
|
|
||||||
|
|
||||||
def authenticate_admin(username: str, password: str) -> Optional[User]:
|
def authenticate_admin(username: str, password: str) -> Optional[User]:
|
||||||
|
|
@ -1098,5 +1100,87 @@ def deploy_batch(
|
||||||
raise click.Abort()
|
raise click.Abort()
|
||||||
|
|
||||||
|
|
||||||
|
@app.command("verify-deployment")
|
||||||
|
@click.option('--batch-id', '-b', required=True, type=int, help='Project/batch ID to verify')
|
||||||
|
@click.option('--sample', '-s', type=int, help='Number of random URLs to check (default: check all)')
|
||||||
|
@click.option('--timeout', '-t', type=int, default=10, help='Request timeout in seconds (default: 10)')
|
||||||
|
def verify_deployment(batch_id: int, sample: Optional[int], timeout: int):
|
||||||
|
"""Verify deployed URLs return 200 OK status"""
|
||||||
|
try:
|
||||||
|
session = db_manager.get_session()
|
||||||
|
|
||||||
|
try:
|
||||||
|
content_repo = GeneratedContentRepository(session)
|
||||||
|
project_repo = ProjectRepository(session)
|
||||||
|
|
||||||
|
project = project_repo.get_by_id(batch_id)
|
||||||
|
if not project:
|
||||||
|
click.echo(f"Error: Project/batch {batch_id} not found", err=True)
|
||||||
|
raise click.Abort()
|
||||||
|
|
||||||
|
click.echo(f"Verifying deployment for batch: {project.name} (ID: {batch_id})")
|
||||||
|
click.echo(f"Keyword: {project.main_keyword}\n")
|
||||||
|
|
||||||
|
articles = content_repo.get_by_project_id(batch_id)
|
||||||
|
deployed_articles = [a for a in articles if a.deployed_url and a.status == 'deployed']
|
||||||
|
|
||||||
|
if not deployed_articles:
|
||||||
|
click.echo("No deployed articles found for this batch.")
|
||||||
|
return
|
||||||
|
|
||||||
|
click.echo(f"Found {len(deployed_articles)} deployed articles")
|
||||||
|
|
||||||
|
urls_to_check = deployed_articles
|
||||||
|
if sample and sample < len(deployed_articles):
|
||||||
|
urls_to_check = random.sample(deployed_articles, sample)
|
||||||
|
click.echo(f"Checking random sample of {sample} URLs\n")
|
||||||
|
else:
|
||||||
|
click.echo(f"Checking all {len(deployed_articles)} URLs\n")
|
||||||
|
|
||||||
|
successful = []
|
||||||
|
failed = []
|
||||||
|
|
||||||
|
for article in urls_to_check:
|
||||||
|
url = article.deployed_url
|
||||||
|
try:
|
||||||
|
response = requests.get(url, timeout=timeout, allow_redirects=True)
|
||||||
|
if response.status_code == 200:
|
||||||
|
successful.append((url, article.title))
|
||||||
|
click.echo(f"✓ {url}")
|
||||||
|
else:
|
||||||
|
failed.append((url, article.title, response.status_code))
|
||||||
|
click.echo(f"✗ {url} (HTTP {response.status_code})")
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
failed.append((url, article.title, str(e)))
|
||||||
|
click.echo(f"✗ {url} (Error: {type(e).__name__})")
|
||||||
|
|
||||||
|
click.echo("\n" + "=" * 70)
|
||||||
|
click.echo("Verification Summary")
|
||||||
|
click.echo("=" * 70)
|
||||||
|
click.echo(f"Total checked: {len(urls_to_check)}")
|
||||||
|
click.echo(f"Successful: {len(successful)}")
|
||||||
|
click.echo(f"Failed: {len(failed)}")
|
||||||
|
|
||||||
|
if failed:
|
||||||
|
click.echo("\nFailed URLs:")
|
||||||
|
for url, title, error in failed:
|
||||||
|
title_preview = title[:50] + "..." if len(title) > 50 else title
|
||||||
|
click.echo(f" {url}")
|
||||||
|
click.echo(f" Title: {title_preview}")
|
||||||
|
click.echo(f" Error: {error}")
|
||||||
|
|
||||||
|
click.echo("=" * 70)
|
||||||
|
|
||||||
|
if failed:
|
||||||
|
raise click.Abort()
|
||||||
|
|
||||||
|
finally:
|
||||||
|
session.close()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
click.echo(f"Error verifying deployment: {e}", err=True)
|
||||||
|
raise click.Abort()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app()
|
app()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue