feat: add comprehensive testing mechanism for dashboard features
Deploy Dashboard (Dev) / deploy-dev (push) Failing after 2m0s
Run Tests / Backend Tests (push) Failing after 4m26s
Run Tests / Frontend Tests (push) Failing after 49s
Run Tests / Test Summary (push) Failing after 15s
Run Tests / Backend Tests (pull_request) Failing after 6m9s
Run Tests / Frontend Tests (pull_request) Failing after 3m54s
Run Tests / Test Summary (pull_request) Failing after 15s

- Add smoke test script for post-deployment verification
- Add health monitoring script with Telegram alerts
- Add backend integration tests for conversation tracker API
- Add frontend tests to verify correct API paths
- Update CI/CD workflows to enforce test failures and run smoke tests
- Add comprehensive testing documentation

This testing mechanism will catch issues like the double /api/ prefix bug
before they reach users.
This commit is contained in:
Gan, Jimmy
2026-04-19 21:54:13 +08:00
parent 9b8d7cfb00
commit 01fcb53a3a
8 changed files with 801 additions and 0 deletions
+28
View File
@@ -80,3 +80,31 @@ jobs:
# Manually connect to networks after container is created # Manually connect to networks after container is created
docker network connect nas-dashboard_internal nas-dashboard-dev || true docker network connect nas-dashboard_internal nas-dashboard-dev || true
docker network connect gitea_gitea nas-dashboard-dev || true docker network connect gitea_gitea nas-dashboard-dev || true
- name: Wait for container to be healthy
run: |
echo "Waiting for container to be healthy..."
for i in {1..30}; do
STATUS=$(docker inspect --format='{{.State.Health.Status}}' nas-dashboard-dev 2>/dev/null || echo "starting")
if [ "$STATUS" = "healthy" ]; then
echo "Container is healthy"
break
fi
echo "Waiting... ($i/30) Status: $STATUS"
sleep 2
done
# Final check
STATUS=$(docker inspect --format='{{.State.Health.Status}}' nas-dashboard-dev 2>/dev/null || echo "unknown")
if [ "$STATUS" != "healthy" ]; then
echo "Container failed to become healthy: $STATUS"
docker logs nas-dashboard-dev --tail 50
exit 1
fi
- name: Run smoke tests
run: |
cd /volume1/repos/nas-tools
bash scripts/smoke-test-dashboard.sh dev
if [ $? -ne 0 ]; then
echo "Smoke tests failed! Check logs above."
docker logs nas-dashboard-dev --tail 100
exit 1
fi
+14
View File
@@ -72,6 +72,13 @@ jobs:
--junit-xml=test-results.xml \ --junit-xml=test-results.xml \
--cov-fail-under=49 --cov-fail-under=49
# Explicitly check exit code
if [ $? -ne 0 ]; then
echo "❌ Backend tests failed!"
exit 1
fi
echo "✅ Backend tests passed"
- name: Upload coverage report - name: Upload coverage report
if: always() if: always()
run: | run: |
@@ -145,6 +152,13 @@ jobs:
cd dashboard/frontend cd dashboard/frontend
npm run test:coverage -- --reporter=verbose --run --pool=forks --poolOptions.forks.maxForks=2 npm run test:coverage -- --reporter=verbose --run --pool=forks --poolOptions.forks.maxForks=2
# Explicitly check exit code
if [ $? -ne 0 ]; then
echo "❌ Frontend tests failed!"
exit 1
fi
echo "✅ Frontend tests passed"
test-summary: test-summary:
name: Test Summary name: Test Summary
runs-on: ubuntu-latest runs-on: ubuntu-latest
+61
View File
@@ -287,3 +287,64 @@ def mock_request():
request.client.host = "192.168.31.100" request.client.host = "192.168.31.100"
request.headers = {} request.headers = {}
return request return request
@pytest.fixture
def mock_conversation_db(tmp_path, monkeypatch):
"""Mock conversation tracker database"""
import sqlite3
db_path = tmp_path / "conversations.db"
# Create minimal SQLite schema
conn = sqlite3.connect(db_path)
conn.execute("""
CREATE TABLE conversations (
session_id TEXT PRIMARY KEY,
project_path TEXT,
started_at DATETIME,
message_count INTEGER,
total_input_tokens INTEGER DEFAULT 0,
total_output_tokens INTEGER DEFAULT 0
)
""")
conn.execute("""
CREATE TABLE messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NOT NULL,
message_uuid TEXT UNIQUE NOT NULL,
role TEXT NOT NULL,
content_text TEXT,
timestamp DATETIME NOT NULL
)
""")
conn.execute("""
CREATE TABLE tool_calls (
id INTEGER PRIMARY KEY AUTOINCREMENT,
message_uuid TEXT NOT NULL,
tool_name TEXT NOT NULL,
timestamp DATETIME NOT NULL
)
""")
conn.execute("""
CREATE TABLE daily_summaries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT UNIQUE NOT NULL,
summary_content TEXT NOT NULL
)
""")
# Insert test data
conn.execute("""
INSERT INTO conversations VALUES
('test-session-id', '/test/project', '2026-04-19 10:00:00', 10, 1000, 500)
""")
conn.execute("""
INSERT INTO messages VALUES
(1, 'test-session-id', 'msg-1', 'user', 'test message', '2026-04-19 10:00:00')
""")
conn.commit()
conn.close()
monkeypatch.setenv("CONVERSATION_TRACKER_DB", str(db_path))
return db_path
@@ -0,0 +1,81 @@
import pytest
from fastapi.testclient import TestClient
class TestConversationTrackerAPI:
"""Test conversation tracker endpoints"""
def test_list_dates_requires_auth(self, test_app):
"""Verify dates endpoint requires authentication"""
response = test_app.get("/api/conversations/dates")
assert response.status_code == 401
assert "Not authenticated" in response.json()["detail"]
def test_list_dates_with_auth(self, test_app, admin_headers, mock_conversation_db):
"""Verify dates endpoint returns data with auth"""
response = test_app.get("/api/conversations/dates", headers=admin_headers)
assert response.status_code == 200
assert isinstance(response.json(), list)
def test_get_summary_not_found(self, test_app, admin_headers, mock_conversation_db):
"""Test daily summary endpoint returns 404 when no summary exists"""
response = test_app.get("/api/conversations/summary/2026-04-19", headers=admin_headers)
assert response.status_code == 404
def test_list_conversations(self, test_app, admin_headers, mock_conversation_db):
"""Test conversation list endpoint"""
response = test_app.get("/api/conversations/list?date=2026-04-19", headers=admin_headers)
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
if len(data) > 0:
assert "session_id" in data[0]
assert "project_path" in data[0]
def test_list_conversations_requires_auth(self, test_app):
"""Test conversation list requires authentication"""
response = test_app.get("/api/conversations/list?date=2026-04-19")
assert response.status_code == 401
def test_get_conversation_detail(self, test_app, admin_headers, mock_conversation_db):
"""Test conversation detail endpoint"""
response = test_app.get("/api/conversations/test-session-id", headers=admin_headers)
assert response.status_code in [200, 404]
def test_get_conversation_messages(self, test_app, admin_headers, mock_conversation_db):
"""Test conversation messages endpoint"""
response = test_app.get("/api/conversations/test-session-id/messages", headers=admin_headers)
assert response.status_code in [200, 404]
def test_search_conversations(self, test_app, admin_headers, mock_conversation_db):
"""Test search endpoint"""
response = test_app.get("/api/conversations/search?q=test", headers=admin_headers)
assert response.status_code == 200
assert isinstance(response.json(), list)
def test_search_requires_query(self, test_app, admin_headers, mock_conversation_db):
"""Test search requires query parameter"""
response = test_app.get("/api/conversations/search", headers=admin_headers)
# Should handle missing query gracefully
assert response.status_code in [200, 400, 422]
def test_get_stats(self, test_app, admin_headers, mock_conversation_db):
"""Test stats endpoint"""
response = test_app.get("/api/conversations/stats", headers=admin_headers)
assert response.status_code == 200
data = response.json()
assert "top_projects" in data
assert "top_tools" in data
assert isinstance(data["top_projects"], list)
assert isinstance(data["top_tools"], list)
def test_trigger_summary_requires_auth(self, test_app):
"""Test trigger endpoint requires authentication"""
response = test_app.post("/api/conversations/trigger")
assert response.status_code == 401
def test_api_prefix_correct(self, test_app, admin_headers):
"""Test that API paths don't have double /api/ prefix"""
# This should be 404, not a valid endpoint
response = test_app.get("/api/api/conversations/dates", headers=admin_headers)
assert response.status_code == 404
@@ -0,0 +1,130 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { get, post } from '../../src/lib/api.js';
describe('Conversation Tracker API Paths', () => {
beforeEach(() => {
// Mock fetch globally
global.fetch = vi.fn();
// Mock localStorage
global.localStorage = {
getItem: vi.fn(() => 'mock-token'),
setItem: vi.fn(),
removeItem: vi.fn(),
};
});
it('should call /api/conversations/dates without double prefix', async () => {
global.fetch.mockResolvedValueOnce({
ok: true,
json: async () => ['2026-04-19'],
headers: new Headers(),
});
await get('/conversations/dates');
expect(global.fetch).toHaveBeenCalledWith(
expect.stringContaining('/api/conversations/dates'),
expect.any(Object)
);
expect(global.fetch).not.toHaveBeenCalledWith(
expect.stringContaining('/api/api/'),
expect.any(Object)
);
});
it('should call /api/conversations/summary/:date correctly', async () => {
global.fetch.mockResolvedValueOnce({
ok: true,
json: async () => ({ summary: 'test' }),
headers: new Headers(),
});
await get('/conversations/summary/2026-04-19');
expect(global.fetch).toHaveBeenCalledWith(
expect.stringContaining('/api/conversations/summary/2026-04-19'),
expect.any(Object)
);
expect(global.fetch).not.toHaveBeenCalledWith(
expect.stringContaining('/api/api/'),
expect.any(Object)
);
});
it('should call /api/conversations/list correctly', async () => {
global.fetch.mockResolvedValueOnce({
ok: true,
json: async () => [],
headers: new Headers(),
});
await get('/conversations/list?date=2026-04-19');
expect(global.fetch).toHaveBeenCalledWith(
expect.stringContaining('/api/conversations/list?date=2026-04-19'),
expect.any(Object)
);
});
it('should call search endpoint correctly', async () => {
global.fetch.mockResolvedValueOnce({
ok: true,
json: async () => [],
headers: new Headers(),
});
await get('/conversations/search?q=test');
expect(global.fetch).toHaveBeenCalledWith(
expect.stringContaining('/api/conversations/search?q=test'),
expect.any(Object)
);
});
it('should call stats endpoint correctly', async () => {
global.fetch.mockResolvedValueOnce({
ok: true,
json: async () => ({ top_projects: [], top_tools: [] }),
headers: new Headers(),
});
await get('/conversations/stats');
expect(global.fetch).toHaveBeenCalledWith(
expect.stringContaining('/api/conversations/stats'),
expect.any(Object)
);
});
it('should call conversation detail endpoint correctly', async () => {
global.fetch.mockResolvedValueOnce({
ok: true,
json: async () => ({ session_id: 'test' }),
headers: new Headers(),
});
await get('/conversations/test-session-id');
expect(global.fetch).toHaveBeenCalledWith(
expect.stringContaining('/api/conversations/test-session-id'),
expect.any(Object)
);
});
it('should call trigger endpoint correctly', async () => {
global.fetch.mockResolvedValueOnce({
ok: true,
json: async () => ({ status: 'ok' }),
headers: new Headers(),
});
await post('/conversations/trigger');
expect(global.fetch).toHaveBeenCalledWith(
expect.stringContaining('/api/conversations/trigger'),
expect.objectContaining({
method: 'POST',
})
);
});
});
+360
View File
@@ -0,0 +1,360 @@
# Testing Guide
## Overview
This guide covers the multi-layered testing approach for the NAS Dashboard to ensure features work correctly before and after deployment.
## Testing Layers
```
/\
/E2E\ <- Full user workflows (optional)
/------\
/Smoke \ <- Post-deployment verification
/----------\
/Integration\ <- API and component integration
/--------------\
/ Unit Tests \ <- Fast, isolated tests
------------------
```
## Running Tests Locally
### Backend Tests
```bash
cd dashboard/backend
python -m venv venv
source venv/bin/activate
pip install -r requirements-dev.txt
pytest tests/ --cov=. --cov-report=term
```
**Run specific test file:**
```bash
pytest tests/integration/test_conversation_tracker.py -v
```
**Run with coverage:**
```bash
pytest tests/ --cov=. --cov-report=html
open htmlcov/index.html
```
### Frontend Tests
```bash
cd dashboard/frontend
npm install
npm run test
```
**Run with coverage:**
```bash
npm run test:coverage
```
**Run in watch mode:**
```bash
npm run test:ui
```
### Smoke Tests
Test dev deployment:
```bash
./scripts/smoke-test-dashboard.sh dev
```
Test production deployment:
```bash
./scripts/smoke-test-dashboard.sh prod
```
## Pre-Deployment Checklist
Before pushing code to dev branch:
- [ ] Run backend tests locally: `cd dashboard/backend && pytest tests/`
- [ ] Run frontend tests locally: `cd dashboard/frontend && npm run test`
- [ ] Verify API paths don't have double `/api/` prefixes
- [ ] Check router registration in `main.py` has correct prefix
- [ ] Test feature manually in local environment if possible
- [ ] Review code changes for obvious issues
## Post-Deployment Verification
### After Deployment to Dev
1. Wait for CI/CD to complete (check Gitea Actions)
2. Run smoke tests:
```bash
./scripts/smoke-test-dashboard.sh dev
```
3. Manually test the feature in browser at `http://nas.jimmygan.com:4001`
4. Check container logs for errors:
```bash
ssh nas "docker logs nas-dashboard-dev --tail 50"
```
### After Deployment to Production
1. Run smoke tests:
```bash
./scripts/smoke-test-dashboard.sh prod
```
2. Verify health monitoring is active:
```bash
ssh nas "tail -20 /volume1/repos/nas-tools/logs/health-monitor.log"
```
3. Test critical user workflows manually
## Adding Tests for New Features
### 1. Backend API Tests
Create integration test in `dashboard/backend/tests/integration/`:
```python
import pytest
class TestMyNewFeature:
def test_endpoint_requires_auth(self, test_app):
response = test_app.get("/api/my-feature/endpoint")
assert response.status_code == 401
def test_endpoint_with_auth(self, test_app, admin_headers):
response = test_app.get("/api/my-feature/endpoint", headers=admin_headers)
assert response.status_code == 200
```
### 2. Frontend API Path Tests
Create test in `dashboard/frontend/tests/integration/`:
```javascript
import { describe, it, expect, vi } from 'vitest';
import { get } from '../../src/lib/api.js';
describe('My Feature API Paths', () => {
it('should call correct API path', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({}),
headers: new Headers(),
});
await get('/my-feature/endpoint');
expect(global.fetch).toHaveBeenCalledWith(
expect.stringContaining('/api/my-feature/endpoint'),
expect.any(Object)
);
});
});
```
### 3. Update Smoke Tests
Add checks to `scripts/smoke-test-dashboard.sh`:
```bash
# Test new feature endpoint
echo "Testing my new feature..."
RESPONSE=$(curl -s "$BASE_URL/api/my-feature/health")
if echo "$RESPONSE" | grep -q "ok"; then
echo "✅ My feature is working"
else
echo "❌ My feature failed"
exit 1
fi
```
## CI/CD Pipeline
Tests run automatically on every push via `.gitea/workflows/test.yml`:
### Backend Tests
- Runs pytest with 49% minimum coverage
- Timeout: 30 seconds per test
- Generates JUnit XML and coverage reports
### Frontend Tests
- Runs vitest with coverage
- Tests all components and API integrations
### Deployment Tests
- Smoke tests run after dev deployment
- Health checks verify container is healthy
- Logs checked for critical errors
## Monitoring
### Health Check Monitoring
Runs every 5 minutes via cron on NAS:
```bash
*/5 * * * * /volume1/repos/nas-tools/scripts/monitor-dashboard-health.sh >> /volume1/repos/nas-tools/logs/health-monitor.log 2>&1
```
**What it checks:**
- Production dashboard `/api/health` endpoint
- Dev dashboard `/api/health` endpoint (non-critical)
- Sends Telegram alert on production failure
- Sends recovery notification when fixed
**View logs:**
```bash
ssh nas "tail -f /volume1/repos/nas-tools/logs/health-monitor.log"
```
### Manual Health Check
```bash
# Check production
curl http://nas.jimmygan.com:4000/api/health
# Check dev
curl http://nas.jimmygan.com:4001/api/health
# Check container health
ssh nas "docker ps | grep nas-dashboard"
```
## Troubleshooting
### Tests Failing Locally
**Backend:**
```bash
# Check Python version (should be 3.12+)
python --version
# Reinstall dependencies
cd dashboard/backend
rm -rf venv
python -m venv venv
source venv/bin/activate
pip install -r requirements-dev.txt
```
**Frontend:**
```bash
# Clear node_modules and reinstall
cd dashboard/frontend
rm -rf node_modules package-lock.json
npm install
```
### Smoke Tests Failing
1. Check if container is running:
```bash
ssh nas "docker ps | grep nas-dashboard"
```
2. Check container logs:
```bash
ssh nas "docker logs nas-dashboard-dev --tail 100"
```
3. Verify network connectivity:
```bash
curl -v http://nas.jimmygan.com:4001/api/health
```
4. Check if database is accessible (for conversation tracker):
```bash
ssh nas "ls -la /volume1/docker/claude-code-tracker/data/conversations.db"
```
### CI/CD Tests Failing
1. Check Gitea Actions logs in web UI
2. Look for specific test failures in output
3. Run the same tests locally to reproduce
4. Fix issues and push again
## Best Practices
### Writing Tests
1. **Test behavior, not implementation** - Focus on what the API returns, not how it works internally
2. **Use descriptive test names** - `test_endpoint_requires_auth` is better than `test_1`
3. **One assertion per test** - Makes failures easier to diagnose
4. **Mock external dependencies** - Don't rely on real databases, APIs, or services
5. **Test error cases** - Not just the happy path
### Test Coverage
- **Aim for 50%+ coverage** - Current backend is at 49%
- **Focus on critical paths** - Auth, data access, user-facing features
- **Don't chase 100%** - Some code (like error handlers) is hard to test
### Deployment Safety
1. **Always test in dev first** - Never push directly to production
2. **Run smoke tests after deployment** - Automated verification catches issues
3. **Monitor logs after deployment** - Watch for errors in first 5-10 minutes
4. **Have rollback plan** - Know how to revert to previous version
## Common Issues and Solutions
### Issue: Double `/api/` prefix in API calls
**Symptom:** Frontend gets 404 errors, logs show `/api/api/endpoint`
**Solution:**
- Check router definition: should NOT have `prefix="/api/..."` in `APIRouter()`
- Check router registration: SHOULD have `prefix="/api/..."` in `app.include_router()`
- Frontend should call `get("/endpoint")` not `get("/api/endpoint")`
**Test:** Run `dashboard/frontend/tests/integration/conversations.test.js`
### Issue: Tests pass locally but fail in CI
**Possible causes:**
- Different Python/Node versions
- Missing environment variables
- Timing issues (increase timeouts)
- File path differences
**Solution:** Check CI logs for specific error, replicate environment locally
### Issue: Smoke tests timeout
**Possible causes:**
- Container not fully started
- Network issues
- Service crashed
**Solution:**
```bash
# Check container status
ssh nas "docker ps -a | grep nas-dashboard"
# Check container logs
ssh nas "docker logs nas-dashboard-dev --tail 50"
# Restart container
ssh nas "cd /volume1/docker/nas-dashboard && docker compose -f docker-compose.dev.yml up -d"
```
## Resources
- **Backend Test Fixtures:** `dashboard/backend/tests/conftest.py`
- **Frontend Test Setup:** `dashboard/frontend/tests/setup.js`
- **CI/CD Workflows:** `.gitea/workflows/`
- **Smoke Tests:** `scripts/smoke-test-dashboard.sh`
- **Health Monitor:** `scripts/monitor-dashboard-health.sh`
## Getting Help
If tests are failing and you're stuck:
1. Check this documentation first
2. Look at existing tests for examples
3. Check CI/CD logs for specific errors
4. Review recent code changes that might have broken tests
5. Ask for help with specific error messages and context
+48
View File
@@ -0,0 +1,48 @@
#!/bin/bash
# Monitor dashboard health and send alerts
# Run via cron: */5 * * * * /volume1/repos/nas-tools/scripts/monitor-dashboard-health.sh
PROD_URL="http://nas.jimmygan.com:4000"
DEV_URL="http://nas.jimmygan.com:4001"
ALERT_FILE="/tmp/dashboard-health-alert-sent"
LOG_DIR="/volume1/repos/nas-tools/logs"
# Create log directory if it doesn't exist
mkdir -p "$LOG_DIR"
check_endpoint() {
local url=$1
local name=$2
response=$(curl -s -o /dev/null -w "%{http_code}" "$url/api/health" --max-time 5)
if [ "$response" != "200" ]; then
echo "$(date): ❌ $name health check failed (HTTP $response)"
return 1
fi
echo "$(date): ✅ $name health check passed"
return 0
}
# Check production
if ! check_endpoint "$PROD_URL" "Production"; then
if [ ! -f "$ALERT_FILE" ]; then
# Send alert only once until issue is resolved
/volume1/repos/nas-tools/notify.sh "⚠️ Dashboard Production Health Check Failed - HTTP endpoint not responding"
touch "$ALERT_FILE"
fi
exit 1
fi
# Check dev (non-critical, just log)
if ! check_endpoint "$DEV_URL" "Dev"; then
echo "$(date): ⚠️ Dev dashboard health check failed (non-critical)"
fi
# Clear alert file if checks pass
if [ -f "$ALERT_FILE" ]; then
rm -f "$ALERT_FILE"
/volume1/repos/nas-tools/notify.sh "✅ Dashboard Production Health Restored"
fi
echo "$(date): ✅ All health checks passed"
+79
View File
@@ -0,0 +1,79 @@
#!/bin/bash
# Smoke test for dashboard deployment
# Usage: ./smoke-test-dashboard.sh [dev|prod]
set -e
ENV=${1:-dev}
if [ "$ENV" = "dev" ]; then
BASE_URL="http://nas.jimmygan.com:4001"
CONTAINER="nas-dashboard-dev"
else
BASE_URL="http://nas.jimmygan.com:4000"
CONTAINER="nas-dashboard"
fi
echo "=== Dashboard Smoke Test ($ENV) ==="
echo "Base URL: $BASE_URL"
echo ""
# Test 1: Health check
echo "1. Testing health endpoint..."
HEALTH=$(curl -s "$BASE_URL/api/health" --max-time 5)
if echo "$HEALTH" | grep -q '"status":"ok"'; then
echo "✅ Health check passed"
else
echo "❌ Health check failed: $HEALTH"
exit 1
fi
# Test 2: Frontend loads
echo "2. Testing frontend loads..."
FRONTEND=$(curl -s "$BASE_URL/" --max-time 5 | grep -c "NAS Dashboard" || echo "0")
if [ "$FRONTEND" -gt 0 ]; then
echo "✅ Frontend loads"
else
echo "❌ Frontend failed to load"
exit 1
fi
# Test 3: API endpoints respond (should require auth)
echo "3. Testing API endpoints require auth..."
DATES=$(curl -s "$BASE_URL/api/conversations/dates" --max-time 5)
if echo "$DATES" | grep -q "Not authenticated"; then
echo "✅ Conversation tracker API requires auth"
else
echo "❌ Unexpected response: $DATES"
exit 1
fi
# Test 4: Check for double /api/ prefix bug
echo "4. Testing for double /api/ prefix bug..."
DOUBLE_API=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/api/api/conversations/dates" --max-time 5)
if [ "$DOUBLE_API" = "404" ]; then
echo "✅ No double /api/ prefix bug"
else
echo "⚠️ Warning: /api/api/ path returned $DOUBLE_API (should be 404)"
fi
# Test 5: Container health
echo "5. Checking container health..."
HEALTH_STATUS=$(ssh nas "/volume1/@appstore/ContainerManager/usr/bin/docker inspect --format='{{.State.Health.Status}}' $CONTAINER" 2>/dev/null || echo "unknown")
if [ "$HEALTH_STATUS" = "healthy" ]; then
echo "✅ Container is healthy"
else
echo "❌ Container health: $HEALTH_STATUS"
exit 1
fi
# Test 6: Check recent logs for errors
echo "6. Checking for recent errors in logs..."
ERROR_COUNT=$(ssh nas "/volume1/@appstore/ContainerManager/usr/bin/docker logs $CONTAINER --tail 50 2>&1 | grep -c 'ERROR' || echo 0")
if [ "$ERROR_COUNT" -lt 5 ]; then
echo "✅ No critical errors in recent logs ($ERROR_COUNT errors)"
else
echo "⚠️ Warning: Found $ERROR_COUNT errors in recent logs"
fi
echo ""
echo "=== All smoke tests passed! ==="