Mobile polish, release validation script, bump to v4.1.2
Mobile-responsive CSS improvements:
- Larger touch targets for drop zones and buttons (56px min)
- Touch feedback with active states for touch devices
- Camera hint text on mobile ("Tap to take photo or choose file")
- Mode buttons stack vertically on small screens
- Full-width download buttons on mobile
- Navbar doesn't stick on mobile to save screen space
Release validation script (scripts/validate-release.sh):
- Automated pre-release checks: ruff, imports, encode/decode sanity
- Optional Docker build/test (--docker flag)
- Optional Pi smoke test via SSH (--pi flag)
- Pass/fail summary with exit codes
Other:
- Version bump to 4.1.2 (pyproject.toml, constants.py, __init__.py)
- Fixed ruff import sorting in cli.py
- Updated PLAN-4.1.2.md (all 9 features complete)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -64,8 +64,9 @@ htmlcov/
|
|||||||
# Output test files.
|
# Output test files.
|
||||||
test_data/*.png
|
test_data/*.png
|
||||||
|
|
||||||
# Dev scripts (local convenience scripts)
|
# Dev scripts (local convenience scripts - except validate-release.sh)
|
||||||
scripts/
|
scripts/*
|
||||||
|
!scripts/validate-release.sh
|
||||||
|
|
||||||
# Web UI auth database and SSL certs
|
# Web UI auth database and SSL certs
|
||||||
frontends/web/instance/
|
frontends/web/instance/
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Polish and UX improvements after the 4.1.1 stability release.
|
|||||||
|
|
||||||
## 1. Real Progress Bar for Encode/Decode
|
## 1. Real Progress Bar for Encode/Decode
|
||||||
|
|
||||||
**Status:** Planned
|
**Status:** Done
|
||||||
|
|
||||||
**Problem:** Users see elapsed time but no indication of how far along the operation is. Long DCT encodes on Pi can take 2-3 minutes with no feedback.
|
**Problem:** Users see elapsed time but no indication of how far along the operation is. Long DCT encodes on Pi can take 2-3 minutes with no feedback.
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ Polish and UX improvements after the 4.1.1 stability release.
|
|||||||
|
|
||||||
## 3. Mobile-Responsive Polish
|
## 3. Mobile-Responsive Polish
|
||||||
|
|
||||||
**Status:** Planned
|
**Status:** Done
|
||||||
|
|
||||||
**Problem:** UI works on mobile but has rough edges - cramped buttons, hard-to-tap targets, awkward layouts on small screens.
|
**Problem:** UI works on mobile but has rough edges - cramped buttons, hard-to-tap targets, awkward layouts on small screens.
|
||||||
|
|
||||||
@@ -203,7 +203,7 @@ Polish and UX improvements after the 4.1.1 stability release.
|
|||||||
|
|
||||||
## 8. Release Validation Script
|
## 8. Release Validation Script
|
||||||
|
|
||||||
**Status:** Planned
|
**Status:** Done
|
||||||
|
|
||||||
**Problem:** Manual release checklist is error-prone. Need automated validation.
|
**Problem:** Manual release checklist is error-prone. Need automated validation.
|
||||||
|
|
||||||
@@ -245,6 +245,6 @@ Polish and UX improvements after the 4.1.1 stability release.
|
|||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- Keep 4.1.2 focused - 9 features (6 done)
|
- Keep 4.1.2 focused - 9 features (9 done)
|
||||||
- Don't break DCT compatibility (4.1.1 RS format is stable)
|
- Don't break DCT compatibility (4.1.1 RS format is stable)
|
||||||
- Test on Pi before release
|
- Test on Pi before release
|
||||||
|
|||||||
@@ -1442,3 +1442,260 @@ footer {
|
|||||||
padding: 0.35rem 0.75rem;
|
padding: 0.35rem 0.75rem;
|
||||||
background: rgba(0, 0, 0, 0.1);
|
background: rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
MOBILE RESPONSIVE IMPROVEMENTS
|
||||||
|
============================================================================ */
|
||||||
|
|
||||||
|
/* Mobile-specific drop zone improvements */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
/* Larger drop zones on mobile for easier touch targets */
|
||||||
|
.drop-zone {
|
||||||
|
padding: 2rem 1.5rem;
|
||||||
|
min-height: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Larger touch target for upload icons */
|
||||||
|
.drop-zone-label i {
|
||||||
|
font-size: 2.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Touch feedback - active state */
|
||||||
|
.drop-zone:active {
|
||||||
|
border-color: var(--gradient-start);
|
||||||
|
background: rgba(102, 126, 234, 0.15);
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mode buttons - stack vertically on very small screens */
|
||||||
|
.d-flex.gap-2:has(.mode-btn) {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-btn {
|
||||||
|
padding: 1rem;
|
||||||
|
min-height: 56px; /* iOS touch target minimum */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Full-width primary buttons */
|
||||||
|
.btn-primary.btn-lg {
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
min-height: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Security factor boxes - more padding for touch */
|
||||||
|
.security-box {
|
||||||
|
padding: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form controls - larger for touch */
|
||||||
|
.form-control,
|
||||||
|
.form-select {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
min-height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input groups - consistent sizing */
|
||||||
|
.input-group .form-control {
|
||||||
|
min-height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group .btn {
|
||||||
|
min-width: 48px;
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Password toggle button - easier to tap */
|
||||||
|
[data-toggle-password] {
|
||||||
|
min-width: 52px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PIN input - larger on mobile */
|
||||||
|
.pin-input-container .form-control {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
letter-spacing: 4px;
|
||||||
|
padding: 0.875rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Passphrase input - comfortable mobile size */
|
||||||
|
.passphrase-input {
|
||||||
|
font-size: 1rem !important;
|
||||||
|
padding: 0.875rem 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card headers - compact on mobile */
|
||||||
|
.card-header h5 {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alert info panel - readable text */
|
||||||
|
.alert.small {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bottom info icons - larger tap targets */
|
||||||
|
.row.text-center .col-4 {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row.text-center .col-4 i {
|
||||||
|
font-size: 2rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Capacity panel badges - easier to read */
|
||||||
|
#capacityPanel .badge {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
padding: 0.4rem 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Payload type toggle - full width buttons */
|
||||||
|
.btn-group[role="group"] {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group .btn {
|
||||||
|
padding: 0.75rem 0.5rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Textarea - comfortable height */
|
||||||
|
textarea.form-control {
|
||||||
|
min-height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Channel select - full width */
|
||||||
|
#channelSelect {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Very small screens (iPhone SE, etc.) */
|
||||||
|
@media (max-width: 375px) {
|
||||||
|
.drop-zone {
|
||||||
|
padding: 1.5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-btn {
|
||||||
|
padding: 0.875rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-btn .text-muted {
|
||||||
|
display: none; /* Hide secondary text on tiny screens */
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header h5 {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stack security factor row */
|
||||||
|
.row:has(.security-box) > .col-md-6 {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Touch device optimizations */
|
||||||
|
@media (hover: none) and (pointer: coarse) {
|
||||||
|
/* Remove hover effects that don't work on touch */
|
||||||
|
.btn-primary:hover {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card:hover {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-link:hover .feature-card {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add active states instead */
|
||||||
|
.btn-primary:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
box-shadow: 0 2px 10px rgba(102, 126, 234, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drop zone active feedback */
|
||||||
|
.drop-zone:active {
|
||||||
|
border-color: var(--gradient-start);
|
||||||
|
background: rgba(102, 126, 234, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mode button active state */
|
||||||
|
.mode-btn:active {
|
||||||
|
background: rgba(255, 255, 255, 0.12);
|
||||||
|
border-color: var(--gradient-start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Camera hint for mobile - shows on file inputs */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.drop-zone-label span.text-muted {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add camera icon hint on mobile */
|
||||||
|
.drop-zone-label::after {
|
||||||
|
content: "Tap to take photo or choose file";
|
||||||
|
display: block;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: rgba(255, 255, 255, 0.4);
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide the default text and show mobile version */
|
||||||
|
.drop-zone-label > span.text-muted {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Navbar mobile adjustments */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.navbar {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand img {
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sticky header shouldn't eat too much space */
|
||||||
|
.navbar.sticky-top {
|
||||||
|
position: relative; /* Don't stick on mobile - saves screen space */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Results page mobile adjustments */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
/* Download button - full width on mobile */
|
||||||
|
.btn-success.btn-lg,
|
||||||
|
a.btn-success.btn-lg {
|
||||||
|
width: 100%;
|
||||||
|
padding: 1rem;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* QR codes - appropriate sizing */
|
||||||
|
.qr-scan-container {
|
||||||
|
max-width: 280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Message display - readable on mobile */
|
||||||
|
.alert-message {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
padding: 1rem;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Result icon - slightly smaller on mobile */
|
||||||
|
.result-icon {
|
||||||
|
font-size: 3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "stegasoo"
|
name = "stegasoo"
|
||||||
version = "4.1.1"
|
version = "4.1.2"
|
||||||
description = "Secure steganography with hybrid photo + passphrase + PIN authentication"
|
description = "Secure steganography with hybrid photo + passphrase + PIN authentication"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
307
scripts/validate-release.sh
Executable file
307
scripts/validate-release.sh
Executable file
@@ -0,0 +1,307 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# Stegasoo Release Validation Script
|
||||||
|
# =============================================================================
|
||||||
|
# Automated pre-release validation to catch issues before tagging a release.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./scripts/validate-release.sh # Local validation only
|
||||||
|
# ./scripts/validate-release.sh --pi # Include Pi smoke test
|
||||||
|
# PI_IP=192.168.0.4 ./scripts/validate-release.sh --pi
|
||||||
|
#
|
||||||
|
# Exit codes:
|
||||||
|
# 0 = All tests passed
|
||||||
|
# 1 = One or more tests failed
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Don't use set -e as we need to handle test failures gracefully
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[0;33m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
# Default Pi IP (can be overridden via environment)
|
||||||
|
PI_IP="${PI_IP:-192.168.0.4}"
|
||||||
|
PI_USER="${PI_USER:-alee}"
|
||||||
|
INCLUDE_PI=false
|
||||||
|
INCLUDE_DOCKER=true
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--pi)
|
||||||
|
INCLUDE_PI=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--no-docker)
|
||||||
|
INCLUDE_DOCKER=false
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--help|-h)
|
||||||
|
echo "Usage: $0 [--pi] [--no-docker]"
|
||||||
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " --pi Include Pi smoke test (requires SSH access)"
|
||||||
|
echo " --no-docker Skip Docker build/test"
|
||||||
|
echo ""
|
||||||
|
echo "Environment:"
|
||||||
|
echo " PI_IP Pi IP address (default: 192.168.0.4)"
|
||||||
|
echo " PI_USER Pi SSH user (default: alee)"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $1"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Track results
|
||||||
|
TESTS_RUN=0
|
||||||
|
TESTS_PASSED=0
|
||||||
|
TESTS_FAILED=0
|
||||||
|
FAILED_TESTS=()
|
||||||
|
|
||||||
|
# Helper functions
|
||||||
|
pass() {
|
||||||
|
echo -e "${GREEN}[PASS]${NC} $1"
|
||||||
|
((TESTS_PASSED++))
|
||||||
|
((TESTS_RUN++))
|
||||||
|
}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo -e "${RED}[FAIL]${NC} $1"
|
||||||
|
FAILED_TESTS+=("$1")
|
||||||
|
((TESTS_FAILED++))
|
||||||
|
((TESTS_RUN++))
|
||||||
|
}
|
||||||
|
|
||||||
|
skip() {
|
||||||
|
echo -e "${YELLOW}[SKIP]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
section() {
|
||||||
|
echo ""
|
||||||
|
echo -e "${CYAN}━━━ $1 ━━━${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Header
|
||||||
|
# =============================================================================
|
||||||
|
echo -e "${CYAN}╔═══════════════════════════════════════════════════════════════╗${NC}"
|
||||||
|
echo -e "${CYAN}║ Stegasoo Release Validation ║${NC}"
|
||||||
|
echo -e "${CYAN}╚═══════════════════════════════════════════════════════════════╝${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Get version from pyproject.toml
|
||||||
|
VERSION=$(grep '^version = ' pyproject.toml | head -1 | cut -d'"' -f2)
|
||||||
|
echo -e "Version: ${YELLOW}${VERSION}${NC}"
|
||||||
|
echo -e "Branch: ${YELLOW}$(git branch --show-current)${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 1. Code Quality Checks
|
||||||
|
# =============================================================================
|
||||||
|
section "Code Quality"
|
||||||
|
|
||||||
|
# Ruff linting
|
||||||
|
if command -v ./venv/bin/ruff &> /dev/null; then
|
||||||
|
echo -n "Running ruff check... "
|
||||||
|
if ./venv/bin/ruff check src/ frontends/ --quiet 2>/dev/null; then
|
||||||
|
pass "Ruff linting"
|
||||||
|
else
|
||||||
|
fail "Ruff linting (run: ./venv/bin/ruff check src/ frontends/)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
skip "Ruff not installed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 2. Unit Tests (if they exist)
|
||||||
|
# =============================================================================
|
||||||
|
section "Unit Tests"
|
||||||
|
|
||||||
|
if ls tests/test_*.py 1> /dev/null 2>&1; then
|
||||||
|
echo -n "Running pytest... "
|
||||||
|
if ./venv/bin/pytest tests/ -q --tb=no 2>/dev/null; then
|
||||||
|
pass "Pytest unit tests"
|
||||||
|
else
|
||||||
|
fail "Pytest unit tests"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
skip "No unit tests found (tests/test_*.py)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 3. Import Tests
|
||||||
|
# =============================================================================
|
||||||
|
section "Import Tests"
|
||||||
|
|
||||||
|
# Test core library import
|
||||||
|
echo -n "Testing stegasoo import... "
|
||||||
|
if ./venv/bin/python -c "from stegasoo import encode, decode; print('OK')" 2>/dev/null | grep -q OK; then
|
||||||
|
pass "Core library import"
|
||||||
|
else
|
||||||
|
fail "Core library import"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test DCT support
|
||||||
|
echo -n "Testing DCT support... "
|
||||||
|
if ./venv/bin/python -c "from stegasoo import has_dct_support; assert has_dct_support(), 'No DCT'; print('OK')" 2>/dev/null | grep -q OK; then
|
||||||
|
pass "DCT support available"
|
||||||
|
else
|
||||||
|
fail "DCT support (scipy/jpegio missing?)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test CLI import
|
||||||
|
echo -n "Testing CLI import... "
|
||||||
|
if ./venv/bin/python -c "from stegasoo.cli import main; print('OK')" 2>/dev/null | grep -q OK; then
|
||||||
|
pass "CLI module import"
|
||||||
|
else
|
||||||
|
fail "CLI module import"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 4. Encode/Decode Sanity Test
|
||||||
|
# =============================================================================
|
||||||
|
section "Encode/Decode Test"
|
||||||
|
|
||||||
|
echo -n "Running encode/decode sanity check... "
|
||||||
|
SANITY_RESULT=$(./venv/bin/python << 'EOF' 2>&1
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, 'src')
|
||||||
|
from stegasoo import encode, decode
|
||||||
|
|
||||||
|
with open('test_data/carrier.jpg', 'rb') as f:
|
||||||
|
carrier = f.read()
|
||||||
|
with open('test_data/ref.jpg', 'rb') as f:
|
||||||
|
ref = f.read()
|
||||||
|
|
||||||
|
# LSB test
|
||||||
|
result = encode(message="sanity test", reference_photo=ref, carrier_image=carrier,
|
||||||
|
passphrase="test", pin="123456", embed_mode="lsb")
|
||||||
|
decoded = decode(stego_image=result.stego_image, reference_photo=ref,
|
||||||
|
passphrase="test", pin="123456", embed_mode="lsb")
|
||||||
|
assert decoded.message == "sanity test", f"LSB mismatch: {decoded.message}"
|
||||||
|
|
||||||
|
# DCT test
|
||||||
|
result = encode(message="dct sanity", reference_photo=ref, carrier_image=carrier,
|
||||||
|
passphrase="dct", pin="654321", embed_mode="dct")
|
||||||
|
decoded = decode(stego_image=result.stego_image, reference_photo=ref,
|
||||||
|
passphrase="dct", pin="654321", embed_mode="dct")
|
||||||
|
assert decoded.message == "dct sanity", f"DCT mismatch: {decoded.message}"
|
||||||
|
|
||||||
|
print("OK")
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
if echo "$SANITY_RESULT" | grep -q "OK"; then
|
||||||
|
pass "Encode/decode sanity (LSB + DCT)"
|
||||||
|
else
|
||||||
|
fail "Encode/decode sanity: $SANITY_RESULT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 5. Docker Build & Test (optional)
|
||||||
|
# =============================================================================
|
||||||
|
if $INCLUDE_DOCKER; then
|
||||||
|
section "Docker"
|
||||||
|
|
||||||
|
if command -v docker &> /dev/null || command -v sudo &> /dev/null; then
|
||||||
|
DOCKER_CMD="docker"
|
||||||
|
if ! docker info &>/dev/null 2>&1; then
|
||||||
|
DOCKER_CMD="sudo docker"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "Building Docker image... "
|
||||||
|
if $DOCKER_CMD build -t stegasoo:validate -q . >/dev/null 2>&1; then
|
||||||
|
pass "Docker build"
|
||||||
|
|
||||||
|
# Test container starts
|
||||||
|
echo -n "Testing container startup... "
|
||||||
|
CONTAINER_ID=$($DOCKER_CMD run -d -p 15000:5000 stegasoo:validate 2>/dev/null)
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
if curl -s -o /dev/null -w "%{http_code}" http://localhost:15000/ 2>/dev/null | grep -qE "200|302"; then
|
||||||
|
pass "Container responds to HTTP"
|
||||||
|
else
|
||||||
|
fail "Container HTTP response"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
$DOCKER_CMD stop "$CONTAINER_ID" >/dev/null 2>&1 || true
|
||||||
|
$DOCKER_CMD rm "$CONTAINER_ID" >/dev/null 2>&1 || true
|
||||||
|
else
|
||||||
|
fail "Docker build"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Cleanup test image
|
||||||
|
$DOCKER_CMD rmi stegasoo:validate >/dev/null 2>&1 || true
|
||||||
|
else
|
||||||
|
skip "Docker not available"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
skip "Docker tests (use --docker to enable)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 6. Pi Smoke Test (optional)
|
||||||
|
# =============================================================================
|
||||||
|
if $INCLUDE_PI; then
|
||||||
|
section "Pi Smoke Test"
|
||||||
|
|
||||||
|
echo -n "Testing SSH connectivity to $PI_USER@$PI_IP... "
|
||||||
|
if ssh -o ConnectTimeout=5 -o BatchMode=yes "$PI_USER@$PI_IP" "echo OK" 2>/dev/null | grep -q OK; then
|
||||||
|
pass "SSH connectivity"
|
||||||
|
|
||||||
|
echo -n "Checking stegasoo service status... "
|
||||||
|
if ssh "$PI_USER@$PI_IP" "systemctl is-active stegasoo" 2>/dev/null | grep -q active; then
|
||||||
|
pass "Stegasoo service running"
|
||||||
|
|
||||||
|
echo -n "Running smoke test on Pi... "
|
||||||
|
SMOKE_RESULT=$(ssh "$PI_USER@$PI_IP" "cd /home/$PI_USER/stegasoo && bash tests/smoke-test.sh --quick 2>&1" || echo "FAILED")
|
||||||
|
if echo "$SMOKE_RESULT" | grep -qE "All tests passed|PASS"; then
|
||||||
|
pass "Pi smoke test"
|
||||||
|
else
|
||||||
|
fail "Pi smoke test"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
fail "Stegasoo service not running"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
fail "SSH connectivity to Pi"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
skip "Pi smoke test (use --pi to enable)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Summary
|
||||||
|
# =============================================================================
|
||||||
|
echo ""
|
||||||
|
echo -e "${CYAN}━━━ Summary ━━━${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e "Tests run: ${TESTS_RUN}"
|
||||||
|
echo -e "Passed: ${GREEN}${TESTS_PASSED}${NC}"
|
||||||
|
echo -e "Failed: ${RED}${TESTS_FAILED}${NC}"
|
||||||
|
|
||||||
|
if [ ${#FAILED_TESTS[@]} -gt 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo -e "${RED}Failed tests:${NC}"
|
||||||
|
for test in "${FAILED_TESTS[@]}"; do
|
||||||
|
echo -e " - $test"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
if [ $TESTS_FAILED -eq 0 ]; then
|
||||||
|
echo -e "${GREEN}✓ All validation checks passed!${NC}"
|
||||||
|
echo -e " Ready to tag release ${VERSION}"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ Validation failed - fix issues before release${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -7,7 +7,7 @@ Changes in v4.0.0:
|
|||||||
- encode() and decode() now accept channel_key parameter
|
- encode() and decode() now accept channel_key parameter
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "4.0.1"
|
__version__ = "4.1.2"
|
||||||
|
|
||||||
# Core functionality
|
# Core functionality
|
||||||
# Channel key management (v4.0.0)
|
# Channel key management (v4.0.0)
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ def encode(
|
|||||||
stegasoo encode photo.png -r ref.jpg -f secret.pdf -o encoded.png
|
stegasoo encode photo.png -r ref.jpg -f secret.pdf -o encoded.png
|
||||||
"""
|
"""
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .encode import encode as stegasoo_encode
|
from .encode import encode as stegasoo_encode
|
||||||
from .encode import encode_file as stegasoo_encode_file
|
from .encode import encode_file as stegasoo_encode_file
|
||||||
|
|
||||||
@@ -1108,7 +1109,9 @@ def admin_recover(db_path, password):
|
|||||||
stegasoo admin recover --db /path/to/stegasoo.db
|
stegasoo admin recover --db /path/to/stegasoo.db
|
||||||
"""
|
"""
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
from argon2 import PasswordHasher
|
from argon2 import PasswordHasher
|
||||||
|
|
||||||
from .recovery import verify_recovery_key
|
from .recovery import verify_recovery_key
|
||||||
|
|
||||||
# Try default paths if not specified
|
# Try default paths if not specified
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ from pathlib import Path
|
|||||||
# VERSION
|
# VERSION
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
__version__ = "4.1.0"
|
__version__ = "4.1.2"
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# FILE FORMAT
|
# FILE FORMAT
|
||||||
|
|||||||
Reference in New Issue
Block a user