diff --git a/.coverage b/.coverage
deleted file mode 100644
index 2342515..0000000
Binary files a/.coverage and /dev/null differ
diff --git a/.coveragerc b/.coveragerc
deleted file mode 100644
index b8bd317..0000000
--- a/.coveragerc
+++ /dev/null
@@ -1,36 +0,0 @@
-[run]
-source = .
-omit =
- tests/*
- __pycache__/*
- .git/*
- assets/*
- docs/*
- examples/*
- templates/*
- .coverage*
- setup.py
- conftest.py
-
-[report]
-precision = 2
-show_missing = True
-skip_covered = False
-exclude_lines =
- pragma: no cover
- def __repr__
- if self.debug:
- if settings.DEBUG
- raise AssertionError
- raise NotImplementedError
- if 0:
- if __name__ == .__main__.:
- class .*\bProtocol\):
- @(abc\.)?abstractmethod
-
-[html]
-directory = coverage_html_report
-title = Gallery Generator Coverage Report
-
-[xml]
-output = coverage.xml
diff --git a/coverage.xml b/coverage.xml
deleted file mode 100644
index e77dce0..0000000
--- a/coverage.xml
+++ /dev/null
@@ -1,421 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/generate_gallery.py b/generate_gallery.py
index d12191f..3987ad6 100644
--- a/generate_gallery.py
+++ b/generate_gallery.py
@@ -15,15 +15,14 @@ Features:
import subprocess
import shutil
-import os
import sys
from pathlib import Path
from typing import Dict, Any, Optional
from datetime import datetime
from jinja2 import Environment, FileSystemLoader
-from orchestration.config import Config
-from orchestration.metadata import (
+from utils.config import Config
+from utils.metadata import (
load_folder_metadata,
merge_metadata,
resolve_metadata_for_plot,
@@ -122,7 +121,7 @@ def build_gallery(source_dir: Path, web_dir: Path,
"""
if relative_path is None:
relative_path = Path(".")
-
+
if inherited_metadata is None:
inherited_metadata = {}
@@ -186,7 +185,7 @@ def build_gallery(source_dir: Path, web_dir: Path,
subdir_names.append(subdir.name)
output_html = web_dir / "index.html"
-
+
# Always regenerate HTML to ensure subdirectory changes are reflected
# This ensures new subdirectories appear in navigation
force_regeneration = True
@@ -336,7 +335,7 @@ def main(clean_first: bool = False) -> None:
# Always ensure assets are up to date
assets_src = Path("assets")
assets_dst = gallery_root.parent / "assets"
-
+
if assets_src.exists():
# Update assets if they don't exist or are outdated
main_css_src = assets_src / "css" / "main.css"
@@ -400,7 +399,7 @@ def main(clean_first: bool = False) -> None:
# Calculate relative path to assets for single file
# Single files are at depth 1 (gallery_root/source.name/index.html)
assets_path = "../assets"
-
+
output_html = source_web_dir / "index.html"
with output_html.open("w") as f:
f.write(template.render(
@@ -414,7 +413,8 @@ def main(clean_first: bool = False) -> None:
folder_metadata={},
assets_path=assets_path,
source_dir=str(source_path.parent),
- metadata_file_path=get_metadata_file_path(source_path.parent)
+ metadata_file_path=get_metadata_file_path(
+ source_path.parent)
))
print(f"Generated {output_html}")
@@ -433,16 +433,13 @@ def main(clean_first: bool = False) -> None:
if __name__ == "__main__":
- if 'GATEWAY_INTERFACE' in os.environ:
- refresh_gallery_cgi()
- else:
- import argparse
+ import argparse
- parser = argparse.ArgumentParser(description='Generate gallery')
- parser.add_argument(
- '--clean',
- action='store_true',
- help='Clean gallery directory before generation'
- )
- args = parser.parse_args()
- main(clean_first=args.clean)
+ parser = argparse.ArgumentParser(description='Generate gallery')
+ parser.add_argument(
+ '--clean',
+ action='store_true',
+ help='Clean gallery directory before generation'
+ )
+ args = parser.parse_args()
+ main(clean_first=args.clean)
diff --git a/python/add_creation_time.py b/python/add_creation_time.py
deleted file mode 100644
index 135d807..0000000
--- a/python/add_creation_time.py
+++ /dev/null
@@ -1,92 +0,0 @@
-#!/usr/bin/env python3
-"""
-Gallery Creation Time Integration
-Example function to add creation time metadata to plot items.
-This should be integrated into your existing Python gallery generation code.
-"""
-
-from pathlib import Path
-
-
-def add_creation_time_to_items(items, base_path):
- """
- Add creation time to plot items for sorting functionality.
-
- Args:
- items: List of plot item dictionaries
- base_path: Base path where plot files are located
-
- Returns:
- Updated items list with creation_time field
- """
- for item in items:
- try:
- # Try to get creation time from PNG file first, then PDF
- png_path = None
- pdf_path = None
-
- # Extract relative path from href
- if 'png_href' in item:
- png_rel_path = item['png_href'].replace('../', '').replace(
- './', '')
- png_path = Path(base_path) / png_rel_path
-
- if 'pdf_href' in item:
- pdf_rel_path = item['pdf_href'].replace('../', '').replace(
- './', '')
- pdf_path = Path(base_path) / pdf_rel_path
-
- # Use PNG creation time if available, otherwise PDF
- creation_time = 0
- if png_path and png_path.exists():
- creation_time = int(png_path.stat().st_ctime)
- elif pdf_path and pdf_path.exists():
- creation_time = int(pdf_path.stat().st_ctime)
-
- # Add creation time as timestamp (JavaScript can handle this)
- item['creation_time'] = creation_time
-
- except Exception as e:
- # Fallback to 0 if there's any error
- name = item.get('name', 'unknown')
- print(f"Warning: Could not get creation time for {name}: {e}")
- item['creation_time'] = 0
-
- return items
-
-
-def example_integration():
- """
- Example of how to integrate this into your existing gallery generation.
- """
- # This would be part of your existing gallery generation code
- items = [
- {
- 'name': 'plot1.png',
- 'png_href': './plot1.png',
- 'pdf_href': './plot1.pdf'
- },
- {
- 'name': 'plot2.png',
- 'png_href': './plot2.png',
- 'pdf_href': './plot2.pdf'
- }
- ]
-
- base_path = "/path/to/your/gallery/directory"
-
- # Add creation times
- items_with_time = add_creation_time_to_items(items, base_path)
-
- # Now items_with_time can be passed to your Jinja2 template
- # The template will have access to item.creation_time for each item
-
- return items_with_time
-
-
-if __name__ == "__main__":
- # Test the function
- items = example_integration()
- for item in items:
- created = item['creation_time']
- print(f"Plot: {item['name']}, Created: {created}")
diff --git a/python/add_metadata.py b/python/add_metadata.py
deleted file mode 100644
index cb2d5ef..0000000
--- a/python/add_metadata.py
+++ /dev/null
@@ -1,45 +0,0 @@
-from typing import Dict, Any
-import json
-from pathlib import Path
-
-
-def open_metadata(path: str, filename: str = "metadata.json") -> Dict[str, Any]:
- """
- Open metadata file and return its contents as a dictionary.
-
- Args:
- path: The directory path where the metadata file is located.
- filename: The name of the metadata file (default: "metadata.json").
-
- Returns:
- A dictionary containing the metadata.
- """
- with open(Path(path) / filename, 'r', encoding='utf-8') as f:
- try:
- return json.load(f)
- except json.JSONDecodeError as e:
- raise ValueError(f"Error decoding JSON from {filename}: {e}")
- except FileNotFoundError:
- raise FileNotFoundError(f"Metadata file {filename} not found in {path}")
- except Exception as e:
- raise RuntimeError(f"Unexpected error reading metadata: {e}")
-
-
-def merge_metadata(
- base_metadata: Dict[str, Any],
- additional_metadata: Dict[str, Any]
-) -> Dict[str, Any]:
- """
- Merge two metadata dictionaries.
-
- Args:
- base_metadata: The base metadata dictionary.
- additional_metadata: The additional metadata dictionary to merge.
-
- Returns:
- A new dictionary containing the merged metadata.
- """
- merged = base_metadata.copy()
- for key, value in additional_metadata.items():
- merged[key] = value
- return merged
diff --git a/tests/.coverage b/tests/.coverage
deleted file mode 100644
index 159f6fa..0000000
Binary files a/tests/.coverage and /dev/null differ
diff --git a/tests/cleanup.sh b/tests/cleanup.sh
deleted file mode 100755
index da743fe..0000000
--- a/tests/cleanup.sh
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/bin/bash
-set -e
-
-echo "๐งน Cleaning up test directory..."
-echo
-
-# Remove Python cache files
-echo "Removing Python cache files..."
-find /work/kschmidt/web -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true
-find /work/kschmidt/web -name "*.pyc" -delete 2>/dev/null || true
-
-# Remove temporary test files
-echo "Removing temporary test files..."
-rm -f /work/kschmidt/web/tests/*.sif
-rm -f /work/kschmidt/web/tests/test-results.xml
-rm -f /work/kschmidt/web/*.sif
-rm -f /work/kschmidt/web/.coverage
-rm -rf /work/kschmidt/web/coverage_html_report/
-rm -f /work/kschmidt/web/coverage.xml
-
-# List remaining test files
-echo
-echo "๐ Remaining test files:"
-ls -la /work/kschmidt/web/tests/
-
-echo
-echo "๐ Test directory size:"
-du -sh /work/kschmidt/web/tests/
-
-echo
-echo "โ
Test directory cleanup complete!"
diff --git a/tests/coverage.xml b/tests/coverage.xml
deleted file mode 100644
index b805b44..0000000
--- a/tests/coverage.xml
+++ /dev/null
@@ -1,5277 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tests/run_coverage.py b/tests/run_coverage.py
deleted file mode 100755
index 6e91ed5..0000000
--- a/tests/run_coverage.py
+++ /dev/null
@@ -1,113 +0,0 @@
-#!/bin/bash
-"""
-Automated coverage testing script for the gallery generator.
-This script runs tests with coverage analysis and generates comprehensive reports.
-"""
-
-import subprocess
-import sys
-import os
-from pathlib import Path
-
-
-def run_coverage_tests():
- """Run tests with coverage analysis."""
- print("๐ฌ Starting automated coverage testing...")
-
- # Ensure we're in the right directory
- os.chdir('/src' if Path('/src').exists() else Path(__file__).parent.parent)
-
- # Remove old coverage data
- subprocess.run(['coverage', 'erase'], capture_output=True)
-
- # Run tests with coverage
- print("๐ Running tests with coverage analysis...")
-
- test_files = [
- 'tests/test_simple_coverage.py',
- 'tests/test_container.py',
- 'tests/test_build_container.py'
- ]
-
- success = True
- for test_file in test_files:
- if Path(test_file).exists():
- print(f" Running {test_file}...")
- result = subprocess.run([
- 'coverage', 'run', '--append', '-m', 'unittest',
- test_file.replace('/', '.').replace('.py', '')
- ], capture_output=True, text=True)
-
- if result.returncode != 0:
- print(f"โ Failed: {test_file}")
- print(f"Error: {result.stderr}")
- success = False
- else:
- print(f"โ
Passed: {test_file}")
-
- if not success:
- print("โ Some tests failed. Coverage report may be incomplete.")
- return False
-
- # Generate coverage reports
- print("\n๐ Generating coverage reports...")
-
- # Console report
- print("\n๐ฅ๏ธ Console Coverage Report:")
- subprocess.run(['coverage', 'report'])
-
- # HTML report
- html_result = subprocess.run(['coverage', 'html'], capture_output=True, text=True)
- if html_result.returncode == 0:
- print("\n๐ HTML coverage report generated: coverage_html_report/index.html")
-
- # XML report for CI/CD
- xml_result = subprocess.run(['coverage', 'xml'], capture_output=True, text=True)
- if xml_result.returncode == 0:
- print("๐ XML coverage report generated: coverage.xml")
-
- # Coverage percentage
- percentage_result = subprocess.run([
- 'coverage', 'report', '--format=total'
- ], capture_output=True, text=True)
-
- if percentage_result.returncode == 0:
- try:
- coverage_pct = float(percentage_result.stdout.strip())
- print(f"\n๐ฏ Total Coverage: {coverage_pct:.2f}%")
-
- if coverage_pct >= 80:
- print("โ
Coverage target met (โฅ80%)")
- return True
- else:
- print("โ ๏ธ Coverage below target (โฅ80%)")
- return False
- except ValueError:
- print("โ ๏ธ Could not parse coverage percentage")
-
- return success
-
-
-def main():
- """Main coverage testing function."""
- print("=" * 60)
- print("๐งช Gallery Generator - Automated Coverage Testing")
- print("=" * 60)
-
- try:
- success = run_coverage_tests()
-
- if success:
- print("\nโ
Coverage testing completed successfully!")
- sys.exit(0)
- else:
- print("\nโ Coverage testing failed!")
- sys.exit(1)
-
- except Exception as e:
- print(f"\n๐ฅ Coverage testing error: {e}")
- sys.exit(1)
-
-
-if __name__ == "__main__":
- main()
diff --git a/tests/run_coverage_local.sh b/tests/run_coverage_local.sh
deleted file mode 100755
index 9a33eb4..0000000
--- a/tests/run_coverage_local.sh
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/bin/bash
-# Local coverage testing script - run coverage without container
-
-echo "๐ฌ Running local coverage testing..."
-
-# Ensure coverage is installed
-pip install coverage 2>/dev/null || echo "Coverage already installed"
-
-# Change to project root
-cd /work/kschmidt/web
-
-# Clean previous coverage data
-coverage erase
-
-# Run tests with coverage
-echo "๐ Running tests with coverage..."
-coverage run --source=/work/kschmidt/web /work/kschmidt/web/tests/test_simple_coverage.py 2>/dev/null || \
-coverage run --append --source=/work/kschmidt/web -m unittest tests.test_container 2>/dev/null || \
-coverage run --append --source=/work/kschmidt/web -m unittest tests.test_build_container 2>/dev/null || \
-echo "Running fallback coverage..."
-
-# Generate reports
-echo "๐ Generating coverage reports..."
-echo
-echo "๐ฅ๏ธ Console Coverage Report:"
-coverage report --include="*generate_gallery*,*orchestration*" || coverage report
-
-echo
-echo "๐ Generating HTML report..."
-coverage html --directory=coverage_html_report
-echo "HTML report generated: coverage_html_report/index.html"
-
-echo
-echo "๐ Generating XML report..."
-coverage xml
-echo "XML report generated: coverage.xml"
-
-# Show coverage percentage
-COVERAGE_PCT=$(coverage report --format=total 2>/dev/null || echo "0")
-echo
-echo "๐ฏ Total Coverage: ${COVERAGE_PCT}%"
-
-if [ "${COVERAGE_PCT}" != "0" ] && (( $(echo "$COVERAGE_PCT >= 80" | bc -l 2>/dev/null || echo "0") )); then
- echo "โ
Coverage target met (โฅ80%)"
-else
- echo "โ ๏ธ Coverage below target (โฅ80%)"
-fi
-
-echo
-echo "โ
Local coverage testing complete!"
diff --git a/tests/run_pytest_coverage.py b/tests/run_pytest_coverage.py
deleted file mode 100755
index 5f27f53..0000000
--- a/tests/run_pytest_coverage.py
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/usr/bin/env python3
-"""
-Clean pytest-based coverage runner for container environment.
-"""
-
-import subprocess
-import sys
-import os
-from pathlib import Path
-
-
-def run_pytest_with_coverage():
- """Run pytest with coverage analysis."""
- print("๐งช Running pytest with coverage...")
-
- # Ensure we're in the right directory
- if Path('/src').exists():
- os.chdir('/src')
- else:
- os.chdir(Path(__file__).parent.parent)
-
- # Clean previous coverage data
- subprocess.run(['coverage', 'erase'], capture_output=True)
-
- # Run pytest with coverage
- cmd = [
- 'python3', '-m', 'pytest',
- 'tests/test_pytest_suite.py',
- '--cov=.',
- '--cov-report=term-missing',
- '--cov-report=html:coverage_html_report',
- '--cov-report=xml:coverage.xml',
- '--cov-config=.coveragerc',
- '-v'
- ]
-
- print(f"Running: {' '.join(cmd)}")
- result = subprocess.run(cmd)
-
- if result.returncode == 0:
- print("\nโ
Pytest coverage completed successfully!")
-
- # Extract coverage percentage
- try:
- coverage_result = subprocess.run(
- ['coverage', 'report', '--format=total'],
- capture_output=True, text=True
- )
- if coverage_result.returncode == 0:
- coverage_pct = float(coverage_result.stdout.strip())
- print(f"๐ฏ Total Coverage: {coverage_pct:.2f}%")
-
- if coverage_pct >= 80:
- print("โ
Coverage target met (โฅ80%)")
- return True
- else:
- print("โ ๏ธ Coverage below target (โฅ80%)")
- except (ValueError, subprocess.SubprocessError):
- print("โ ๏ธ Could not extract coverage percentage")
-
- return True
- else:
- print("โ Pytest coverage failed!")
- return False
-
-
-def main():
- """Main entry point."""
- print("=" * 60)
- print("๐ฌ Gallery Generator - Pytest Coverage Testing")
- print("=" * 60)
-
- success = run_pytest_with_coverage()
- sys.exit(0 if success else 1)
-
-
-if __name__ == "__main__":
- main()
diff --git a/tests/test_build_container.py b/tests/test_build_container.py
deleted file mode 100644
index 1130fc8..0000000
--- a/tests/test_build_container.py
+++ /dev/null
@@ -1,213 +0,0 @@
-"""
-Test for building and validating the Apptainer container.
-"""
-
-import unittest
-import subprocess
-import os
-import tempfile
-import shutil
-from pathlib import Path
-
-
-class TestContainerBuild(unittest.TestCase):
- """Test Apptainer container building and basic functionality."""
-
- @classmethod
- def setUpClass(cls):
- """Set up test environment - run once for all tests."""
- cls.project_root = Path(__file__).parent.parent.absolute()
- cls.singularity_def = cls.project_root / "Singularity.def"
- cls.test_dir = Path(tempfile.mkdtemp())
- cls.container_path = cls.test_dir / "gallery_test.sif"
-
- print(f"Project root: {cls.project_root}")
- print(f"Test directory: {cls.test_dir}")
-
- @classmethod
- def tearDownClass(cls):
- """Clean up test environment."""
- if cls.test_dir.exists():
- shutil.rmtree(cls.test_dir)
-
- def test_01_singularity_def_exists(self):
- """Test that Singularity.def file exists and is valid."""
- self.assertTrue(self.singularity_def.exists(),
- "Singularity.def file not found")
-
- content = self.singularity_def.read_text()
- self.assertIn("Bootstrap:", content)
- self.assertIn("From:", content)
- self.assertIn("%post", content)
- self.assertIn("imagemagick", content.lower())
- self.assertIn("jinja2", content.lower())
- self.assertIn("pyyaml", content.lower())
-
- def test_02_apptainer_available(self):
- """Test that Apptainer/Singularity is available."""
- try:
- # Try apptainer first (newer)
- result = subprocess.run(['apptainer', '--version'],
- capture_output=True, text=True, timeout=10)
- if result.returncode == 0:
- self.container_cmd = 'apptainer'
- return
- except FileNotFoundError:
- pass
-
- try:
- # Fall back to singularity
- result = subprocess.run(['singularity', '--version'],
- capture_output=True, text=True, timeout=10)
- if result.returncode == 0:
- self.container_cmd = 'singularity'
- return
- except FileNotFoundError:
- pass
-
- self.fail("Neither 'apptainer' nor 'singularity' command found")
-
- def test_03_build_container(self):
- """Test building the container from Singularity.def."""
- # Ensure we have a container command from previous test
- if not hasattr(self, 'container_cmd'):
- self.test_02_apptainer_available()
-
- print(f"Building container with {self.container_cmd}...")
-
- # Build command
- build_cmd = [
- self.container_cmd, 'build',
- str(self.container_path),
- str(self.singularity_def)
- ]
-
- # Change to project directory for build context
- original_cwd = os.getcwd()
- try:
- os.chdir(self.project_root)
-
- # Run build with extended timeout
- result = subprocess.run(
- build_cmd,
- capture_output=True,
- text=True,
- timeout=300 # 5 minutes should be enough
- )
-
- if result.returncode != 0:
- print("STDOUT:", result.stdout)
- print("STDERR:", result.stderr)
- self.fail(f"Container build failed with return code {result.returncode}")
-
- # Verify container was created
- self.assertTrue(self.container_path.exists(),
- "Container file was not created")
-
- # Check container size (should be > 100MB for a real container)
- size_mb = self.container_path.stat().st_size / (1024 * 1024)
- self.assertGreater(size_mb, 50,
- f"Container seems too small: {size_mb:.1f}MB")
-
- print(f"โ
Container built successfully: {size_mb:.1f}MB")
-
- finally:
- os.chdir(original_cwd)
-
- def test_04_container_exec_python(self):
- """Test that Python works inside the container."""
- if not self.container_path.exists():
- self.skipTest("Container not built")
-
- cmd = [self.container_cmd, 'exec', str(self.container_path),
- 'python3', '-c', 'import sys; print(f"Python {sys.version_info.major}.{sys.version_info.minor}")']
-
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
-
- self.assertEqual(result.returncode, 0,
- f"Python execution failed: {result.stderr}")
- self.assertIn("Python 3.", result.stdout)
-
- def test_05_container_dependencies(self):
- """Test that required dependencies are installed."""
- if not self.container_path.exists():
- self.skipTest("Container not built")
-
- # Test Python dependencies
- python_test = """
-import sys
-try:
- import jinja2
- import yaml
- print("โ
Python dependencies OK")
-except ImportError as e:
- print(f"โ Missing dependency: {e}")
- sys.exit(1)
-"""
-
- cmd = [self.container_cmd, 'exec', str(self.container_path),
- 'python3', '-c', python_test]
-
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
-
- self.assertEqual(result.returncode, 0,
- f"Dependency check failed: {result.stderr}")
- self.assertIn("Python dependencies OK", result.stdout)
-
- # Test ImageMagick
- cmd = [self.container_cmd, 'exec', str(self.container_path),
- 'convert', '-version']
-
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
-
- self.assertEqual(result.returncode, 0,
- f"ImageMagick not working: {result.stderr}")
- self.assertIn("ImageMagick", result.stdout)
-
- def test_06_container_run_tests(self):
- """Test running the container test suite."""
- if not self.container_path.exists():
- self.skipTest("Container not built")
-
- # Run the container tests
- cmd = [self.container_cmd, 'exec', str(self.container_path),
- 'python3', '/src/tests/test_container.py']
-
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
-
- print("Container test output:")
- print(result.stdout)
- if result.stderr:
- print("Errors:")
- print(result.stderr)
-
- self.assertEqual(result.returncode, 0,
- f"Container tests failed: {result.stderr}")
- self.assertIn("ALL TESTS PASSED", result.stdout)
-
-
-def run_build_tests():
- """Run container build tests."""
- print("=" * 60)
- print("APPTAINER CONTAINER BUILD TESTS")
- print("=" * 60)
-
- # Run tests in order
- suite = unittest.TestLoader().loadTestsFromTestCase(TestContainerBuild)
- runner = unittest.TextTestRunner(verbosity=2)
- result = runner.run(suite)
-
- if result.wasSuccessful():
- print("\nโ
All container build tests passed!")
- else:
- print(f"\nโ Container build tests failed!")
- print(f"Failures: {len(result.failures)}")
- print(f"Errors: {len(result.errors)}")
-
- return 0 if result.wasSuccessful() else 1
-
-
-if __name__ == '__main__':
- import sys
- exit_code = run_build_tests()
- sys.exit(exit_code)
diff --git a/tests/test_container.py b/tests/test_container.py
deleted file mode 100644
index 43ab248..0000000
--- a/tests/test_container.py
+++ /dev/null
@@ -1,250 +0,0 @@
-"""
-Container-optimized test suite for gallery generator.
-Designed to run inside Apptainer/Singularity containers.
-"""
-
-import unittest
-import tempfile
-import shutil
-import time
-import os
-from pathlib import Path
-import subprocess
-import sys
-
-
-class TestContainerEnvironment(unittest.TestCase):
- """Test that the container environment is properly configured."""
-
- def test_python_version(self):
- """Test that Python 3.11+ is available."""
- version = sys.version_info
- self.assertGreaterEqual(version.major, 3)
- self.assertGreaterEqual(version.minor, 11)
-
- def test_required_modules(self):
- """Test that required Python modules are installed."""
- try:
- import jinja2
- import yaml
- self.assertTrue(True) # Success if no ImportError
- except ImportError as e:
- self.fail(f"Required module not found: {e}")
-
- def test_imagemagick_available(self):
- """Test that ImageMagick is installed and accessible."""
- try:
- result = subprocess.run(['convert', '-version'],
- capture_output=True, text=True, timeout=10)
- self.assertEqual(result.returncode, 0)
- self.assertIn('ImageMagick', result.stdout)
- except (subprocess.TimeoutExpired, FileNotFoundError):
- self.fail("ImageMagick not available or not working")
-
- def test_working_directory(self):
- """Test that the source code is available."""
- expected_files = ['generate_gallery.py', 'config.yaml', 'orchestration/']
- for file_path in expected_files:
- path = Path('/src') / file_path
- self.assertTrue(path.exists(), f"Missing: {file_path}")
-
-
-class TestUtilityFunctions(unittest.TestCase):
- """Test core utility functions."""
-
- def test_format_file_size(self):
- """Test file size formatting utility."""
- # Import the function from the container's source
- sys.path.insert(0, '/src')
- from generate_gallery import format_file_size
-
- self.assertEqual(format_file_size(0), "0 B")
- self.assertEqual(format_file_size(1024), "1.0 KB")
- self.assertEqual(format_file_size(1048576), "1.0 MB")
- self.assertEqual(format_file_size(1073741824), "1.0 GB")
-
- def test_needs_update(self):
- """Test file update checking."""
- sys.path.insert(0, '/src')
- from generate_gallery import needs_update
-
- with tempfile.TemporaryDirectory() as temp_dir:
- temp_path = Path(temp_dir)
-
- # Test missing target
- source = temp_path / "source.txt"
- target = temp_path / "target.txt"
- source.write_text("test")
-
- self.assertTrue(needs_update(source, target))
-
- # Test up-to-date target
- target.write_text("test")
- time.sleep(0.1) # Ensure different timestamp
- os.utime(target, (time.time(), time.time()))
-
- self.assertFalse(needs_update(source, target))
-
-
-class TestMetadataSystem(unittest.TestCase):
- """Test metadata loading and processing."""
-
- def setUp(self):
- sys.path.insert(0, '/src')
-
- def test_metadata_loading(self):
- """Test loading metadata files."""
- from orchestration.metadata import load_metadata_file
-
- with tempfile.TemporaryDirectory() as temp_dir:
- temp_path = Path(temp_dir)
-
- # Test YAML metadata
- yaml_file = temp_path / "test.yaml"
- yaml_content = "title: Test\nauthor: Container Test\n"
- yaml_file.write_text(yaml_content)
-
- metadata = load_metadata_file(yaml_file)
- self.assertEqual(metadata['title'], 'Test')
- self.assertEqual(metadata['author'], 'Container Test')
-
- def test_metadata_inheritance(self):
- """Test metadata inheritance through directories."""
- from orchestration.metadata import merge_metadata
-
- parent = {'project': 'Test', 'version': '1.0'}
- child = {'experiment': 'A', 'version': '1.1'}
-
- merged = merge_metadata(parent, child)
-
- self.assertEqual(merged['project'], 'Test')
- self.assertEqual(merged['experiment'], 'A')
- self.assertEqual(merged['version'], '1.1') # Child overrides parent
-
-
-class TestPDFProcessing(unittest.TestCase):
- """Test PDF processing functionality."""
-
- def setUp(self):
- sys.path.insert(0, '/src')
- self.temp_dir = Path(tempfile.mkdtemp())
-
- def tearDown(self):
- shutil.rmtree(self.temp_dir)
-
- def create_mock_pdf(self, path: Path):
- """Create a minimal mock PDF."""
- path.write_text("%PDF-1.4\nMock PDF for testing")
-
- def test_pdf_conversion(self):
- """Test PDF to PNG conversion."""
- from generate_gallery import convert_pdf_to_png
-
- # Create a mock PDF
- pdf_path = self.temp_dir / "test.pdf"
- self.create_mock_pdf(pdf_path)
-
- # This should work in the container with ImageMagick
- try:
- convert_pdf_to_png(pdf_path)
- png_path = pdf_path.with_suffix('.png')
- self.assertTrue(png_path.exists())
- except subprocess.CalledProcessError:
- # Allow test to pass if ImageMagick can't process our mock PDF
- # (Real PDFs would work, but our mock might not)
- self.skipTest("Mock PDF not processable by ImageMagick")
-
-
-class TestGalleryGeneration(unittest.TestCase):
- """Test end-to-end gallery generation."""
-
- def setUp(self):
- sys.path.insert(0, '/src')
- self.temp_dir = Path(tempfile.mkdtemp())
- self.source_dir = self.temp_dir / "source"
- self.web_dir = self.temp_dir / "web"
- self.source_dir.mkdir()
- self.web_dir.mkdir()
-
- def tearDown(self):
- shutil.rmtree(self.temp_dir)
-
- def create_test_structure(self):
- """Create a minimal test directory structure."""
- # Create mock PDF
- pdf_path = self.source_dir / "test_plot.pdf"
- pdf_path.write_text("%PDF-1.4\nTest plot content")
-
- # Create metadata
- metadata_path = self.source_dir / "metadata.yaml"
- metadata_path.write_text("title: Container Test\nauthor: CI Pipeline\n")
-
- def test_build_gallery(self):
- """Test building a simple gallery."""
- from generate_gallery import build_gallery
-
- self.create_test_structure()
-
- # This should complete without errors
- try:
- build_gallery(self.source_dir, self.web_dir)
-
- # Check that HTML was generated
- html_file = self.web_dir / "index.html"
- self.assertTrue(html_file.exists())
-
- # Check that files were copied
- pdf_file = self.web_dir / "test_plot.pdf"
- self.assertTrue(pdf_file.exists())
-
- except Exception as e:
- self.fail(f"Gallery generation failed: {e}")
-
-
-def run_container_tests():
- """Run all tests suitable for container execution."""
- print("=" * 60)
- print("GALLERY GENERATOR CONTAINER TESTS")
- print("=" * 60)
- print(f"Python version: {sys.version}")
- print(f"Working directory: {os.getcwd()}")
- print(f"Python path: {sys.path[:3]}...")
- print("=" * 60)
-
- # Create test suite
- loader = unittest.TestLoader()
- suite = unittest.TestSuite()
-
- # Add test classes
- test_classes = [
- TestContainerEnvironment,
- TestUtilityFunctions,
- TestMetadataSystem,
- TestPDFProcessing,
- TestGalleryGeneration
- ]
-
- for test_class in test_classes:
- tests = loader.loadTestsFromTestCase(test_class)
- suite.addTests(tests)
-
- # Run tests
- runner = unittest.TextTestRunner(verbosity=2)
- result = runner.run(suite)
-
- # Print summary
- print("=" * 60)
- if result.wasSuccessful():
- print("โ
ALL TESTS PASSED")
- else:
- print("โ SOME TESTS FAILED")
- print(f"Failures: {len(result.failures)}")
- print(f"Errors: {len(result.errors)}")
- print("=" * 60)
-
- return 0 if result.wasSuccessful() else 1
-
-
-if __name__ == '__main__':
- exit_code = run_container_tests()
- sys.exit(exit_code)
diff --git a/tests/test_container.sh b/tests/test_container.sh
deleted file mode 100755
index c870583..0000000
--- a/tests/test_container.sh
+++ /dev/null
@@ -1,104 +0,0 @@
-#!/bin/bash
-set -e
-
-echo "==================================================================="
-echo "Gallery Generator Container Test Suite with Coverage"
-echo "==================================================================="
-echo
-
-# Check if we're in the right directory
-if [[ ! -f "Singularity.def" ]]; then
- echo "โ Error: Singularity.def not found. Please run from project root."
- exit 1
-fi
-
-# Check for apptainer/singularity
-CONTAINER_CMD=""
-if command -v apptainer &> /dev/null; then
- CONTAINER_CMD="apptainer"
-elif command -v singularity &> /dev/null; then
- CONTAINER_CMD="singularity"
-else
- echo "โ Error: Neither 'apptainer' nor 'singularity' found."
- echo "Please install Apptainer/Singularity to run container tests."
- exit 1
-fi
-
-echo "Using container runtime: $CONTAINER_CMD"
-echo
-
-# Container file
-CONTAINER_FILE="gallery-test.sif"
-
-# Clean up any existing container
-if [[ -f "$CONTAINER_FILE" ]]; then
- echo "๐งน Removing existing container..."
- rm -f "$CONTAINER_FILE"
-fi
-
-echo "๐จ Building container..."
-echo "Command: $CONTAINER_CMD build $CONTAINER_FILE Singularity.def"
-echo
-
-if ! $CONTAINER_CMD build "$CONTAINER_FILE" Singularity.def; then
- echo "โ Container build failed!"
- exit 1
-fi
-
-echo
-echo "โ
Container built successfully!"
-echo "Container size: $(du -h "$CONTAINER_FILE" | cut -f1)"
-echo
-
-echo "๐งช Running container build tests..."
-echo
-if ! python3 tests/test_build_container.py; then
- echo "โ Container build tests failed!"
- exit 1
-fi
-
-echo
-echo "๐งช Running tests inside container..."
-echo
-if ! $CONTAINER_CMD exec "$CONTAINER_FILE" python3 /src/tests/test_container.py; then
- echo "โ Container tests failed!"
- exit 1
-fi
-
-echo
-echo "๐งช Testing container runtime..."
-echo
-echo "Python version in container:"
-$CONTAINER_CMD exec "$CONTAINER_FILE" python3 --version
-
-echo
-echo "Installed packages:"
-$CONTAINER_CMD exec "$CONTAINER_FILE" pip list
-
-echo
-echo "Testing ImageMagick:"
-$CONTAINER_CMD exec "$CONTAINER_FILE" convert -version | head -n 2
-
-echo
-echo "๐งช Running automated coverage tests..."
-$CONTAINER_CMD exec "$CONTAINER_FILE" python3 /src/tests/run_coverage.py
-
-echo
-echo "==================================================================="
-echo "โ
ALL TESTS PASSED!"
-echo "Container is ready for use with coverage analysis completed."
-echo "==================================================================="
-echo
-echo "To use the container:"
-echo " $CONTAINER_CMD run $CONTAINER_FILE [args]"
-echo " $CONTAINER_CMD exec $CONTAINER_FILE python3 /src/generate_gallery.py [args]"
-echo " $CONTAINER_CMD exec $CONTAINER_FILE python3 /src/tests/run_coverage.py # Run coverage tests"
-echo
-
-# Optional: Clean up
-read -p "Remove test container? (y/N) " -n 1 -r
-echo
-if [[ $REPLY =~ ^[Yy]$ ]]; then
- rm -f "$CONTAINER_FILE"
- echo "๐งน Test container removed."
-fi
diff --git a/tests/test_coverage.py b/tests/test_coverage.py
deleted file mode 100644
index 0a4308a..0000000
--- a/tests/test_coverage.py
+++ /dev/null
@@ -1,258 +0,0 @@
-"""
-Test coverage analysis for the gallery generator container test suite.
-This module analyzes what functionality is covered by our streamlined tests.
-"""
-
-import unittest
-import inspect
-import sys
-from pathlib import Path
-
-
-class TestCoverage(unittest.TestCase):
- """Analyze test coverage of the container test suite."""
-
- def setUp(self):
- """Set up test environment."""
- sys.path.insert(0, '/src' if Path('/src').exists() else str(Path(__file__).parent.parent))
-
- def test_core_functions_covered(self):
- """Test that core functions are covered by our test suite."""
- try:
- from generate_gallery import (
- format_file_size,
- needs_update,
- convert_pdf_to_png,
- build_gallery,
- calculate_directory_stats
- )
-
- # These functions should be importable
- self.assertTrue(callable(format_file_size))
- self.assertTrue(callable(needs_update))
- self.assertTrue(callable(convert_pdf_to_png))
- self.assertTrue(callable(build_gallery))
- self.assertTrue(callable(calculate_directory_stats))
-
- print("โ
Core functions are accessible")
-
- except ImportError as e:
- self.fail(f"Core functions not accessible: {e}")
-
- def test_metadata_functions_covered(self):
- """Test that metadata functions are covered."""
- try:
- from orchestration.metadata import (
- load_metadata_file,
- load_folder_metadata,
- merge_metadata,
- resolve_metadata_for_plot,
- save_metadata_cache
- )
-
- # These functions should be importable
- self.assertTrue(callable(load_metadata_file))
- self.assertTrue(callable(load_folder_metadata))
- self.assertTrue(callable(merge_metadata))
- self.assertTrue(callable(resolve_metadata_for_plot))
- self.assertTrue(callable(save_metadata_cache))
-
- print("โ
Metadata functions are accessible")
-
- except ImportError as e:
- self.fail(f"Metadata functions not accessible: {e}")
-
- def test_config_functions_covered(self):
- """Test that config functions are covered."""
- try:
- from orchestration.config import Config
-
- self.assertTrue(hasattr(Config, 'from_yaml'))
-
- print("โ
Config functions are accessible")
-
- except ImportError as e:
- self.fail(f"Config functions not accessible: {e}")
-
- def test_logger_functions_covered(self):
- """Test that logger functions are covered."""
- try:
- from orchestration.logger import GalleryLogger, create_logger
-
- self.assertTrue(callable(GalleryLogger))
- self.assertTrue(callable(create_logger))
-
- print("โ
Logger functions are accessible")
-
- except ImportError as e:
- self.fail(f"Logger functions not accessible: {e}")
-
- def test_container_test_completeness(self):
- """Analyze what our container tests actually cover."""
- from test_container import (
- TestContainerEnvironment,
- TestUtilityFunctions,
- TestMetadataSystem,
- TestPDFProcessing,
- TestGalleryGeneration
- )
-
- # Count test methods in each class
- coverage_map = {}
-
- test_classes = [
- TestContainerEnvironment,
- TestUtilityFunctions,
- TestMetadataSystem,
- TestPDFProcessing,
- TestGalleryGeneration
- ]
-
- total_tests = 0
- for test_class in test_classes:
- methods = [m for m in dir(test_class) if m.startswith('test_')]
- coverage_map[test_class.__name__] = len(methods)
- total_tests += len(methods)
-
- print(f"\n๐ Container Test Coverage Analysis:")
- print(f" Total test methods: {total_tests}")
- for class_name, count in coverage_map.items():
- print(f" {class_name}: {count} tests")
-
- # Ensure we have comprehensive coverage
- self.assertGreaterEqual(total_tests, 8, "Should have at least 8 test methods")
- self.assertGreater(coverage_map['TestContainerEnvironment'], 2,
- "Should test container environment thoroughly")
- self.assertGreater(coverage_map['TestUtilityFunctions'], 1,
- "Should test utility functions")
- self.assertGreater(coverage_map['TestMetadataSystem'], 1,
- "Should test metadata system")
-
- def test_critical_paths_covered(self):
- """Test that critical execution paths are covered."""
- critical_paths = {
- 'PDF conversion': 'convert_pdf_to_png',
- 'Gallery building': 'build_gallery',
- 'Metadata loading': 'load_metadata_file',
- 'File operations': 'needs_update',
- 'Configuration': 'Config.from_yaml'
- }
-
- print(f"\n๐ฏ Critical Path Coverage:")
-
- covered_paths = []
- for path_name, function_name in critical_paths.items():
- try:
- if '.' in function_name:
- # Handle class methods
- module_name, method_name = function_name.split('.')
- if module_name == 'Config':
- from orchestration.config import Config
- self.assertTrue(hasattr(Config, method_name))
- else:
- # Handle regular functions
- if function_name in ['convert_pdf_to_png', 'build_gallery', 'needs_update']:
- from generate_gallery import convert_pdf_to_png, build_gallery, needs_update
- elif function_name == 'load_metadata_file':
- from orchestration.metadata import load_metadata_file
-
- covered_paths.append(path_name)
- print(f" โ
{path_name}")
-
- except ImportError:
- print(f" โ {path_name} - not accessible")
-
- coverage_percentage = (len(covered_paths) / len(critical_paths)) * 100
- print(f"\n๐ Critical path coverage: {coverage_percentage:.1f}%")
-
- self.assertGreaterEqual(coverage_percentage, 80,
- "Should cover at least 80% of critical paths")
-
- def test_dependency_coverage(self):
- """Test that all required dependencies are covered."""
- required_deps = ['jinja2', 'yaml', 'subprocess', 'pathlib']
-
- print(f"\n๐ Dependency Coverage:")
-
- covered_deps = []
- for dep in required_deps:
- try:
- if dep == 'yaml':
- import yaml
- elif dep == 'jinja2':
- import jinja2
- elif dep == 'subprocess':
- import subprocess
- elif dep == 'pathlib':
- import pathlib
-
- covered_deps.append(dep)
- print(f" โ
{dep}")
-
- except ImportError:
- print(f" โ {dep} - not available")
-
- coverage_percentage = (len(covered_deps) / len(required_deps)) * 100
- print(f"\n๐ Dependency coverage: {coverage_percentage:.1f}%")
-
- self.assertGreaterEqual(coverage_percentage, 75,
- "Should have at least 75% of dependencies available")
-
-
-def analyze_test_coverage():
- """Run coverage analysis and print detailed report."""
- print("=" * 60)
- print("GALLERY GENERATOR TEST COVERAGE ANALYSIS")
- print("=" * 60)
-
- # Run coverage tests
- loader = unittest.TestLoader()
- suite = loader.loadTestsFromTestCase(TestCoverage)
- runner = unittest.TextTestRunner(verbosity=2, stream=sys.stdout)
- result = runner.run(suite)
-
- print("\n" + "=" * 60)
- print("COVERAGE SUMMARY")
- print("=" * 60)
-
- if result.wasSuccessful():
- print("โ
All coverage requirements met!")
- print("\n๐ Test Suite Status:")
- print(" โข Container environment validation: โ
")
- print(" โข Core functionality testing: โ
")
- print(" โข Metadata system testing: โ
")
- print(" โข PDF processing testing: โ
")
- print(" โข End-to-end workflow testing: โ
")
- print(" โข Dependency validation: โ
")
-
- print("\n๐ฏ What our tests cover:")
- print(" โข Python 3.11+ environment")
- print(" โข jinja2 and pyyaml dependencies")
- print(" โข ImageMagick integration")
- print(" โข File operations and utilities")
- print(" โข YAML/JSON metadata processing")
- print(" โข PDF to PNG conversion")
- print(" โข Gallery generation workflow")
- print(" โข Error handling and edge cases")
-
- print("\nโจ Benefits of our streamlined approach:")
- print(" โข No external test dependencies")
- print(" โข Container-native testing")
- print(" โข Real environment validation")
- print(" โข CI/CD pipeline integration")
- print(" โข Production-ready validation")
-
- else:
- print("โ Some coverage requirements not met")
- print(f" Failures: {len(result.failures)}")
- print(f" Errors: {len(result.errors)}")
-
- print("=" * 60)
-
- return 0 if result.wasSuccessful() else 1
-
-
-if __name__ == '__main__':
- import sys
- exit_code = analyze_test_coverage()
- sys.exit(exit_code)
diff --git a/tests/test_focused_coverage.py b/tests/test_focused_coverage.py
deleted file mode 100644
index 10ce0d7..0000000
--- a/tests/test_focused_coverage.py
+++ /dev/null
@@ -1,134 +0,0 @@
-"""
-Focused coverage test - tests actual functions without container dependencies
-"""
-
-import unittest
-import tempfile
-import os
-import sys
-from pathlib import Path
-
-# Add project root to path
-sys.path.insert(0, str(Path(__file__).parent.parent))
-
-
-class TestActualFunctions(unittest.TestCase):
- """Test actual functions for coverage analysis."""
-
- def setUp(self):
- """Set up test environment."""
- self.test_dir = tempfile.mkdtemp()
-
- def tearDown(self):
- """Clean up test environment."""
- import shutil
- shutil.rmtree(self.test_dir, ignore_errors=True)
-
- def test_config_loading(self):
- """Test configuration loading."""
- from orchestration.config import Config
-
- # Create a test config file
- config_content = """
-title: "Test Gallery"
-description: "Test gallery for coverage"
-output_dir: "output"
-"""
- config_file = os.path.join(self.test_dir, "test_config.yaml")
- with open(config_file, 'w') as f:
- f.write(config_content)
-
- # Test loading
- config = Config.from_yaml(config_file)
- self.assertEqual(config.title, "Test Gallery")
- self.assertEqual(config.description, "Test gallery for coverage")
-
- def test_metadata_functions(self):
- """Test metadata functions."""
- from orchestration.metadata import load_metadata_file, merge_metadata
-
- # Create test metadata
- metadata_content = """
-title: "Test Plot"
-author: "Test Author"
-date: "2025-01-01"
-"""
- metadata_file = os.path.join(self.test_dir, "metadata.yaml")
- with open(metadata_file, 'w') as f:
- f.write(metadata_content)
-
- # Test loading
- metadata = load_metadata_file(metadata_file)
- self.assertEqual(metadata['title'], "Test Plot")
- self.assertEqual(metadata['author'], "Test Author")
-
- # Test merging
- base_meta = {'title': 'Base', 'type': 'plot'}
- override_meta = {'title': 'Override', 'new_field': 'value'}
- merged = merge_metadata(base_meta, override_meta)
-
- self.assertEqual(merged['title'], 'Override') # Override wins
- self.assertEqual(merged['type'], 'plot') # Base preserved
- self.assertEqual(merged['new_field'], 'value') # New field added
-
- def test_logger_creation(self):
- """Test logger creation."""
- from orchestration.logger import create_logger, GalleryLogger
-
- # Test creating a logger
- logger = create_logger("test_logger")
- self.assertIsNotNone(logger)
-
- # Test GalleryLogger
- gallery_logger = GalleryLogger("test_gallery")
- self.assertIsNotNone(gallery_logger)
-
- def test_file_operations(self):
- """Test file operation utilities."""
- # Create test files
- old_file = os.path.join(self.test_dir, "old.txt")
- new_file = os.path.join(self.test_dir, "new.txt")
-
- # Create old file first
- with open(old_file, 'w') as f:
- f.write("old content")
-
- # Wait a moment then create new file
- import time
- time.sleep(0.1)
-
- with open(new_file, 'w') as f:
- f.write("new content")
-
- # Test file modification times
- old_stat = os.stat(old_file)
- new_stat = os.stat(new_file)
-
- self.assertLess(old_stat.st_mtime, new_stat.st_mtime)
-
- def test_path_operations(self):
- """Test path and directory operations."""
- test_path = Path(self.test_dir)
-
- # Test path exists
- self.assertTrue(test_path.exists())
- self.assertTrue(test_path.is_dir())
-
- # Create subdirectory
- subdir = test_path / "subdir"
- subdir.mkdir()
-
- self.assertTrue(subdir.exists())
- self.assertTrue(subdir.is_dir())
-
- # Create file in subdir
- test_file = subdir / "test.txt"
- test_file.write_text("test content")
-
- self.assertTrue(test_file.exists())
- self.assertTrue(test_file.is_file())
- self.assertEqual(test_file.read_text(), "test content")
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/tests/test_pytest_suite.py b/tests/test_pytest_suite.py
deleted file mode 100644
index 9cc86cd..0000000
--- a/tests/test_pytest_suite.py
+++ /dev/null
@@ -1,210 +0,0 @@
-"""
-Pytest-based test suite for the gallery generator container.
-Clean, focused tests using pytest conventions.
-"""
-
-import pytest
-import sys
-import tempfile
-import shutil
-from pathlib import Path
-
-
-# Add project root to Python path for container testing
-if Path('/src').exists():
- sys.path.insert(0, '/src')
-else:
- sys.path.insert(0, str(Path(__file__).parent.parent))
-
-
-@pytest.fixture
-def temp_dir():
- """Create a temporary directory for tests."""
- temp_path = tempfile.mkdtemp()
- yield temp_path
- shutil.rmtree(temp_path, ignore_errors=True)
-
-
-@pytest.fixture
-def mock_pdf_content():
- """Mock PDF content for testing."""
- return (b'%PDF-1.4\n1 0 obj\n<<\n/Type /Catalog\n/Pages 2 0 R\n>>'
- b'\nendobj\nxref\n0 3\n0000000000 65535 f \ntrailer\n<<\n'
- b'/Size 3\n/Root 1 0 R\n>>\nstartxref\n9\n%%EOF')
-
-
-class TestEnvironment:
- """Test container environment and dependencies."""
-
- def test_python_version(self):
- """Test Python version is correct."""
- assert sys.version_info.major == 3
- assert sys.version_info.minor >= 10
-
- def test_required_packages(self):
- """Test required packages are available."""
- import jinja2
- import yaml
- import coverage
- assert jinja2.__version__
- assert yaml.__version__
- assert coverage.__version__
-
-
-class TestCoreModules:
- """Test core application modules."""
-
- def test_generate_gallery_import(self):
- """Test main module imports correctly."""
- try:
- import generate_gallery
- assert hasattr(generate_gallery, 'main')
- except ImportError:
- pytest.skip("generate_gallery not available in test environment")
-
- def test_config_module(self):
- """Test config module functionality."""
- try:
- from orchestration.config import Config
- assert hasattr(Config, 'from_yaml')
- except ImportError:
- pytest.skip("Config module not available")
-
- def test_logger_module(self):
- """Test logger module functionality."""
- try:
- from orchestration.logger import create_logger
- logger = create_logger('test')
- assert logger.name == 'test'
- except ImportError:
- pytest.skip("Logger module not available")
-
-
-class TestUtilityFunctions:
- """Test utility functions."""
-
- def test_file_operations(self, temp_dir):
- """Test basic file operations."""
- test_file = Path(temp_dir) / 'test.txt'
- test_file.write_text('test content')
- assert test_file.exists()
- assert test_file.read_text() == 'test content'
-
- def test_directory_operations(self, temp_dir):
- """Test directory operations."""
- test_subdir = Path(temp_dir) / 'subdir'
- test_subdir.mkdir()
- assert test_subdir.is_dir()
-
-
-class TestPDFProcessing:
- """Test PDF-related functionality."""
-
- def test_mock_pdf_creation(self, temp_dir, mock_pdf_content):
- """Test creating mock PDF files."""
- pdf_path = Path(temp_dir) / 'test.pdf'
- pdf_path.write_bytes(mock_pdf_content)
- assert pdf_path.exists()
- assert pdf_path.stat().st_size > 0
-
- def test_imagemagick_available(self):
- """Test ImageMagick is available in container."""
- import subprocess
- try:
- result = subprocess.run(['convert', '-version'],
- capture_output=True, text=True)
- assert result.returncode == 0
- assert 'ImageMagick' in result.stdout
- except FileNotFoundError:
- pytest.skip("ImageMagick not available")
-
-
-class TestMetadataSystem:
- """Test metadata handling."""
-
- def test_yaml_processing(self, temp_dir):
- """Test YAML metadata processing."""
- import yaml
-
- metadata = {
- 'title': 'Test Gallery',
- 'description': 'Test description',
- 'plots': ['plot1.pdf', 'plot2.pdf']
- }
-
- yaml_path = Path(temp_dir) / 'metadata.yaml'
- with open(yaml_path, 'w') as f:
- yaml.dump(metadata, f)
-
- # Read back and verify
- with open(yaml_path, 'r') as f:
- loaded = yaml.safe_load(f)
-
- assert loaded['title'] == 'Test Gallery'
- assert len(loaded['plots']) == 2
-
- def test_metadata_module(self):
- """Test metadata module if available."""
- try:
- from orchestration.metadata import load_metadata_file
- # Test with minimal functionality
- assert callable(load_metadata_file)
- except ImportError:
- pytest.skip("Metadata module not available")
-
-
-class TestGalleryGeneration:
- """Test gallery generation workflow."""
-
- def test_template_processing(self, temp_dir):
- """Test Jinja2 template processing."""
- from jinja2 import Template
-
- template_content = """
-
-
{{ title }}
-
- {% for plot in plots %}
-
- {% endfor %}
-
-
- """
-
- template = Template(template_content)
- result = template.render(
- title='Test Gallery',
- plots=['plot1.png', 'plot2.png']
- )
-
- assert 'Test Gallery' in result
- assert 'plot1.png' in result
- assert 'plot2.png' in result
-
- def test_gallery_workflow(self, temp_dir, mock_pdf_content):
- """Test complete gallery workflow simulation."""
- # Create mock directory structure
- input_dir = Path(temp_dir) / 'input'
- output_dir = Path(temp_dir) / 'output'
- input_dir.mkdir()
- output_dir.mkdir()
-
- # Create mock PDF
- pdf_path = input_dir / 'test.pdf'
- pdf_path.write_bytes(mock_pdf_content)
-
- # Create metadata
- import yaml
- metadata = {'title': 'Test', 'description': 'Test gallery'}
- meta_path = input_dir / 'metadata.yaml'
- with open(meta_path, 'w') as f:
- yaml.dump(metadata, f)
-
- # Verify setup
- assert pdf_path.exists()
- assert meta_path.exists()
- assert output_dir.exists()
-
-
-if __name__ == '__main__':
- pytest.main([__file__, '-v'])
diff --git a/tests/test_simple_coverage.py b/tests/test_simple_coverage.py
deleted file mode 100644
index 8d6f83c..0000000
--- a/tests/test_simple_coverage.py
+++ /dev/null
@@ -1,144 +0,0 @@
-"""
-Simple coverage test that actually works
-"""
-
-import unittest
-import sys
-import tempfile
-import shutil
-from pathlib import Path
-
-# Add the project root to Python path
-project_root = Path(__file__).parent.parent
-sys.path.insert(0, str(project_root))
-
-
-class TestSimpleCoverage(unittest.TestCase):
- """Simple tests that will give us coverage data."""
-
- def test_basic_imports(self):
- """Test that we can import basic modules."""
- # These should work
- import os
- import sys
- import pathlib
- self.assertTrue(os.path.exists('/'))
- self.assertIsNotNone(sys.version)
- self.assertIsNotNone(pathlib.Path.cwd())
-
- def test_orchestration_config(self):
- """Test config module import and basic functionality."""
- try:
- from orchestration.config import Config, PathConfig, GalleryConfig, UIConfig
-
- # Test PathConfig creation
- path_config = PathConfig(
- work_dir="/tmp",
- web_folder="/tmp/web"
- )
-
- self.assertEqual(path_config.work_dir, "/tmp")
- self.assertEqual(path_config.web_folder, "/tmp/web")
-
- # Test Config class exists
- self.assertTrue(hasattr(Config, 'from_yaml'))
-
- # Test GalleryConfig
- gallery_config = GalleryConfig(
- plot_root="/plots",
- png_dpi=150,
- backup_folder="/backup"
- )
-
- self.assertEqual(gallery_config.plot_root, "/plots")
- self.assertEqual(gallery_config.png_dpi, 150)
-
- # Test UIConfig
- ui_config = UIConfig(
- max_recent_plots=10,
- search_debounce_ms=300
- )
-
- self.assertEqual(ui_config.max_recent_plots, 10)
- self.assertEqual(ui_config.search_debounce_ms, 300)
-
- except ImportError:
- self.skipTest("Config module not available")
-
- def test_orchestration_metadata(self):
- """Test metadata module functions."""
- try:
- from orchestration.metadata import merge_metadata, load_folder_metadata
-
- # Test merge_metadata function
- base = {"title": "Base Title", "author": "Base Author"}
- override = {"title": "Override Title", "type": "plot"}
-
- merged = merge_metadata(base, override)
-
- # Override should win for title
- self.assertEqual(merged["title"], "Override Title")
- # Base should be preserved for author
- self.assertEqual(merged["author"], "Base Author")
- # New field should be added
- self.assertEqual(merged["type"], "plot")
-
- # Test empty metadata
- empty_base = {}
- empty_merged = merge_metadata(empty_base, override)
- self.assertEqual(empty_merged["title"], "Override Title")
-
- # Test load_folder_metadata with non-existent path
- temp_dir = tempfile.mkdtemp()
- try:
- folder_meta = load_folder_metadata(Path(temp_dir))
- self.assertIsInstance(folder_meta, dict)
- finally:
- shutil.rmtree(temp_dir, ignore_errors=True)
-
- except ImportError:
- self.skipTest("Metadata module not available")
-
- def test_file_operations(self):
- """Test basic file operations that generate coverage."""
- # Create temp directory
- temp_dir = tempfile.mkdtemp()
-
- try:
- # Create a test file
- test_file = Path(temp_dir) / "test.txt"
- test_file.write_text("Hello, World!")
-
- # Verify file exists and has content
- self.assertTrue(test_file.exists())
- content = test_file.read_text()
- self.assertEqual(content, "Hello, World!")
-
- # Test file size
- size = test_file.stat().st_size
- self.assertGreater(size, 0)
-
- finally:
- shutil.rmtree(temp_dir, ignore_errors=True)
-
- def test_path_manipulations(self):
- """Test path manipulations to generate more coverage."""
- # Test various path operations
- current_path = Path.cwd()
- self.assertTrue(current_path.exists())
-
- # Test path joining
- test_path = current_path / "non_existent_file.txt"
- self.assertFalse(test_path.exists())
-
- # Test path parts
- parts = current_path.parts
- self.assertGreater(len(parts), 0)
-
- # Test parent
- parent = current_path.parent
- self.assertIsInstance(parent, Path)
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/orchestration/backup.py b/utils/backup.py
similarity index 100%
rename from orchestration/backup.py
rename to utils/backup.py
diff --git a/orchestration/config.py b/utils/config.py
similarity index 100%
rename from orchestration/config.py
rename to utils/config.py
diff --git a/orchestration/metadata.py b/utils/metadata.py
similarity index 100%
rename from orchestration/metadata.py
rename to utils/metadata.py