Matches the convention used by pyPhotoAlbum and the other projects here:
runs-on: linux/amd64 with a container image from the Gitea registry,
instead of setup-python plus an ad-hoc `pip install pytest pytest-cov
flake8 coverage-badge interrogate` on a self-hosted runner.
pyWebLayout is a library, so the image carries all four interpreters
pyproject.toml claims to support - 3.10, 3.11, 3.12, 3.13 - each in its
own venv at /opt/py<version> with every dependency pre-installed. The
matrix picks one per job. A CI run now downloads nothing, and coverage
widens from 3.10/3.12/3.13 to the full declared range.
Ubuntu marks its system Python externally-managed, so per-interpreter
venvs are used rather than --break-system-packages; that also keeps the
four dependency sets isolated.
Two defects in the existing workflow are fixed while rewriting it:
- pytest runs under continue-on-error so the badge steps still execute,
but nothing afterwards checked its outcome - the job reported green on
a red suite. An explicit gate now fails the job.
- Every matrix leg ran the badge steps and force-pushed the badges
branch, so three jobs raced to publish. Badges and artifacts are now
produced by the 3.13 leg only.
setuptools is pinned below 81 in the image: that release dropped
pkg_resources, which coverage-badge imports at startup, and without the
pin the badge step dies with ModuleNotFoundError. Found by running the
workflow's own commands in the image rather than assuming they work.
Also raises the test Flask server's readiness budget from 5s to 30s.
Making that check raise instead of silently falling through (737cf07)
turned runner load into a hard failure; it showed up as 17 spurious
errors in one containerised run and did not reproduce in three repeats.
The loop still exits as soon as the server answers.
Verified locally: image builds, and tests/ passes 916 on each of 3.10,
3.11, 3.12 and 3.13 inside it. The publishing leg was run end to end -
clean-install dependency check, pytest with coverage, both badges,
coverage summary at 81.5%.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
601 lines
20 KiB
Python
601 lines
20 KiB
Python
"""
|
|
Unit tests for abstract block elements.
|
|
|
|
Tests the core abstract block classes that form the foundation of the document model.
|
|
"""
|
|
|
|
import unittest
|
|
import os
|
|
import tempfile
|
|
import shutil
|
|
import threading
|
|
import time
|
|
from PIL import Image as PILImage
|
|
from pyWebLayout.abstract.block import (
|
|
BlockType, Paragraph, Heading, HeadingLevel, Quote, CodeBlock, HList,
|
|
ListStyle, ListItem, Table, TableRow, TableCell, HorizontalRule,
|
|
Image
|
|
)
|
|
from pyWebLayout.abstract.inline import Word, LineBreak
|
|
from pyWebLayout.style import Font
|
|
|
|
# Flask server for testing URL functionality
|
|
try:
|
|
from flask import Flask, send_file
|
|
FLASK_AVAILABLE = True
|
|
except ImportError:
|
|
FLASK_AVAILABLE = False
|
|
|
|
|
|
class TestBlockElements(unittest.TestCase):
|
|
"""Test cases for basic block elements."""
|
|
|
|
def test_paragraph_creation(self):
|
|
"""Test creating and using paragraphs."""
|
|
paragraph = Paragraph()
|
|
|
|
self.assertEqual(paragraph.block_type, BlockType.PARAGRAPH)
|
|
self.assertEqual(paragraph.word_count, 0)
|
|
self.assertIsNone(paragraph.parent)
|
|
|
|
# Add words
|
|
font = Font()
|
|
word1 = Word("Hello", font)
|
|
word2 = Word("World", font)
|
|
|
|
paragraph.add_word(word1)
|
|
paragraph.add_word(word2)
|
|
|
|
self.assertEqual(paragraph.word_count, 2)
|
|
|
|
# Test word iteration
|
|
words = list(paragraph.words_iter())
|
|
self.assertEqual(len(words), 2)
|
|
self.assertEqual(words[0][1].text, "Hello")
|
|
self.assertEqual(words[1][1].text, "World")
|
|
|
|
def test_heading_levels(self):
|
|
"""Test heading creation with different levels."""
|
|
h1 = Heading(HeadingLevel.H1)
|
|
h3 = Heading(HeadingLevel.H3)
|
|
h6 = Heading(HeadingLevel.H6)
|
|
|
|
self.assertEqual(h1.level, HeadingLevel.H1)
|
|
self.assertEqual(h3.level, HeadingLevel.H3)
|
|
self.assertEqual(h6.level, HeadingLevel.H6)
|
|
|
|
self.assertEqual(h1.block_type, BlockType.HEADING)
|
|
|
|
# Test level modification
|
|
h1.level = HeadingLevel.H2
|
|
self.assertEqual(h1.level, HeadingLevel.H2)
|
|
|
|
def test_quote_nesting(self):
|
|
"""Test blockquote with nested content."""
|
|
quote = Quote()
|
|
|
|
# Add nested paragraphs
|
|
p1 = Paragraph()
|
|
p2 = Paragraph()
|
|
|
|
quote.add_block(p1)
|
|
quote.add_block(p2)
|
|
|
|
self.assertEqual(p1.parent, quote)
|
|
self.assertEqual(p2.parent, quote)
|
|
|
|
# Test block iteration
|
|
blocks = list(quote.blocks())
|
|
self.assertEqual(len(blocks), 2)
|
|
self.assertEqual(blocks[0], p1)
|
|
self.assertEqual(blocks[1], p2)
|
|
|
|
def test_code_block(self):
|
|
"""Test code block functionality."""
|
|
code = CodeBlock("python")
|
|
|
|
self.assertEqual(code.language, "python")
|
|
self.assertEqual(code.line_count, 0)
|
|
|
|
# Add code lines
|
|
code.add_line("def hello():")
|
|
code.add_line(" print('Hello!')")
|
|
|
|
self.assertEqual(code.line_count, 2)
|
|
|
|
# Test line iteration
|
|
lines = list(code.lines())
|
|
self.assertEqual(len(lines), 2)
|
|
self.assertEqual(lines[0][1], "def hello():")
|
|
self.assertEqual(lines[1][1], " print('Hello!')")
|
|
|
|
# Test language modification
|
|
code.language = "javascript"
|
|
self.assertEqual(code.language, "javascript")
|
|
|
|
def test_list_creation(self):
|
|
"""Test list creation and item management."""
|
|
# Unordered list
|
|
ul = HList(ListStyle.UNORDERED)
|
|
self.assertEqual(ul.style, ListStyle.UNORDERED)
|
|
self.assertEqual(ul.item_count, 0)
|
|
|
|
# Add list items
|
|
item1 = ListItem()
|
|
item2 = ListItem()
|
|
|
|
ul.add_item(item1)
|
|
ul.add_item(item2)
|
|
|
|
self.assertEqual(ul.item_count, 2)
|
|
self.assertEqual(item1.parent, ul)
|
|
self.assertEqual(item2.parent, ul)
|
|
|
|
# Test item iteration
|
|
items = list(ul.items())
|
|
self.assertEqual(len(items), 2)
|
|
|
|
# Test list style change
|
|
ul.style = ListStyle.ORDERED
|
|
self.assertEqual(ul.style, ListStyle.ORDERED)
|
|
|
|
def test_definition_list(self):
|
|
"""Test definition list with terms."""
|
|
dl = HList(ListStyle.DEFINITION)
|
|
|
|
# Add definition items with terms
|
|
dt1 = ListItem(term="Python")
|
|
dt2 = ListItem(term="JavaScript")
|
|
|
|
dl.add_item(dt1)
|
|
dl.add_item(dt2)
|
|
|
|
self.assertEqual(dt1.term, "Python")
|
|
self.assertEqual(dt2.term, "JavaScript")
|
|
|
|
# Test term modification
|
|
dt1.term = "Python 3"
|
|
self.assertEqual(dt1.term, "Python 3")
|
|
|
|
def test_table_structure(self):
|
|
"""Test table, row, and cell structure."""
|
|
table = Table(caption="Test Table")
|
|
|
|
self.assertEqual(table.caption, "Test Table")
|
|
self.assertEqual(table.row_count["total"], 0)
|
|
|
|
# Create rows and cells
|
|
header_row = TableRow()
|
|
data_row = TableRow()
|
|
|
|
# Header cells
|
|
h1 = TableCell(is_header=True)
|
|
h2 = TableCell(is_header=True)
|
|
header_row.add_cell(h1)
|
|
header_row.add_cell(h2)
|
|
|
|
# Data cells
|
|
d1 = TableCell(is_header=False)
|
|
d2 = TableCell(is_header=False, colspan=2)
|
|
data_row.add_cell(d1)
|
|
data_row.add_cell(d2)
|
|
|
|
# Add rows to table
|
|
table.add_row(header_row, "header")
|
|
table.add_row(data_row, "body")
|
|
|
|
# Test structure
|
|
self.assertEqual(table.row_count["header"], 1)
|
|
self.assertEqual(table.row_count["body"], 1)
|
|
self.assertEqual(table.row_count["total"], 2)
|
|
|
|
# Test cell properties
|
|
self.assertTrue(h1.is_header)
|
|
self.assertFalse(d1.is_header)
|
|
self.assertEqual(d2.colspan, 2)
|
|
self.assertEqual(d2.rowspan, 1) # Default
|
|
|
|
# Test row cell count
|
|
self.assertEqual(header_row.cell_count, 2)
|
|
self.assertEqual(data_row.cell_count, 2)
|
|
|
|
def test_table_sections(self):
|
|
"""Test table header, body, and footer sections."""
|
|
table = Table()
|
|
|
|
# Add rows to different sections
|
|
header = TableRow()
|
|
body1 = TableRow()
|
|
body2 = TableRow()
|
|
footer = TableRow()
|
|
|
|
table.add_row(header, "header")
|
|
table.add_row(body1, "body")
|
|
table.add_row(body2, "body")
|
|
table.add_row(footer, "footer")
|
|
|
|
# Test section iteration
|
|
header_rows = list(table.header_rows())
|
|
body_rows = list(table.body_rows())
|
|
footer_rows = list(table.footer_rows())
|
|
|
|
self.assertEqual(len(header_rows), 1)
|
|
self.assertEqual(len(body_rows), 2)
|
|
self.assertEqual(len(footer_rows), 1)
|
|
|
|
# Test all_rows iteration
|
|
all_rows = list(table.all_rows())
|
|
self.assertEqual(len(all_rows), 4)
|
|
|
|
# Check section labels
|
|
sections = [section for section, row in all_rows]
|
|
self.assertEqual(sections, ["header", "body", "body", "footer"])
|
|
|
|
def test_image_loading(self):
|
|
"""Test image element properties."""
|
|
# Test with basic properties
|
|
img = Image("test.jpg", "Test image", 100, 200)
|
|
|
|
self.assertEqual(img.source, "test.jpg")
|
|
self.assertEqual(img.alt_text, "Test image")
|
|
self.assertEqual(img.width, 100)
|
|
self.assertEqual(img.height, 200)
|
|
|
|
# Test property modification
|
|
img.source = "new.png"
|
|
img.alt_text = "New image"
|
|
img.width = 150
|
|
img.height = 300
|
|
|
|
self.assertEqual(img.source, "new.png")
|
|
self.assertEqual(img.alt_text, "New image")
|
|
self.assertEqual(img.width, 150)
|
|
self.assertEqual(img.height, 300)
|
|
|
|
# Test dimensions tuple
|
|
self.assertEqual(img.get_dimensions(), (150, 300))
|
|
|
|
def test_aspect_ratio_calculation(self):
|
|
"""Test image aspect ratio calculations."""
|
|
# Test with specified dimensions
|
|
img = Image("test.jpg", width=400, height=200)
|
|
self.assertEqual(img.get_aspect_ratio(), 2.0) # 400/200
|
|
|
|
# Test with only one dimension
|
|
img2 = Image("test.jpg", width=300)
|
|
self.assertIsNone(img2.get_aspect_ratio()) # No height specified
|
|
|
|
# Test scaled dimensions
|
|
scaled = img.calculate_scaled_dimensions(max_width=200, max_height=150)
|
|
# Should scale down proportionally
|
|
self.assertEqual(scaled[0], 200) # Width limited by max_width
|
|
self.assertEqual(scaled[1], 100) # Height scaled proportionally
|
|
|
|
def test_simple_elements(self):
|
|
"""Test simple block elements."""
|
|
hr = HorizontalRule()
|
|
br = LineBreak()
|
|
|
|
self.assertEqual(hr.block_type, BlockType.HORIZONTAL_RULE)
|
|
self.assertEqual(br.block_type, BlockType.LINE_BREAK)
|
|
|
|
# These elements have no additional properties
|
|
self.assertIsNone(hr.parent)
|
|
self.assertIsNone(br.parent)
|
|
|
|
|
|
class TestImagePIL(unittest.TestCase):
|
|
"""Test cases for Image class with PIL functionality."""
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
"""Set up temporary directory and test images."""
|
|
cls.temp_dir = tempfile.mkdtemp()
|
|
cls.sample_image_path = "tests/data/sample_image.jpg"
|
|
|
|
# Create test images in different formats
|
|
cls._create_test_images()
|
|
|
|
# Start Flask server for URL testing if Flask is available
|
|
if FLASK_AVAILABLE:
|
|
cls._start_flask_server()
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
"""Clean up temporary directory and stop Flask server."""
|
|
shutil.rmtree(cls.temp_dir, ignore_errors=True)
|
|
|
|
if FLASK_AVAILABLE and hasattr(cls, 'flask_thread'):
|
|
cls.flask_server_running = False
|
|
cls.flask_server.shutdown()
|
|
cls.flask_server.server_close()
|
|
cls.flask_thread.join(timeout=2)
|
|
|
|
@classmethod
|
|
def _create_test_images(cls):
|
|
"""Create test images in different formats."""
|
|
# Load the sample image
|
|
if os.path.exists(cls.sample_image_path):
|
|
with PILImage.open(cls.sample_image_path) as img:
|
|
cls.original_size = img.size
|
|
|
|
# Save in different formats
|
|
cls.jpg_path = os.path.join(cls.temp_dir, "test.jpg")
|
|
cls.png_path = os.path.join(cls.temp_dir, "test.png")
|
|
cls.bmp_path = os.path.join(cls.temp_dir, "test.bmp")
|
|
cls.gif_path = os.path.join(cls.temp_dir, "test.gif")
|
|
|
|
img.save(cls.jpg_path, "JPEG")
|
|
img.save(cls.png_path, "PNG")
|
|
img.save(cls.bmp_path, "BMP")
|
|
|
|
# Convert to RGB for GIF (GIF doesn't support transparency from RGBA)
|
|
rgb_img = img.convert("RGB")
|
|
rgb_img.save(cls.gif_path, "GIF")
|
|
else:
|
|
# Create a simple test image if sample doesn't exist
|
|
cls.original_size = (100, 100)
|
|
test_img = PILImage.new("RGB", cls.original_size, (255, 0, 0))
|
|
|
|
cls.jpg_path = os.path.join(cls.temp_dir, "test.jpg")
|
|
cls.png_path = os.path.join(cls.temp_dir, "test.png")
|
|
cls.bmp_path = os.path.join(cls.temp_dir, "test.bmp")
|
|
cls.gif_path = os.path.join(cls.temp_dir, "test.gif")
|
|
|
|
test_img.save(cls.jpg_path, "JPEG")
|
|
test_img.save(cls.png_path, "PNG")
|
|
test_img.save(cls.bmp_path, "BMP")
|
|
test_img.save(cls.gif_path, "GIF")
|
|
|
|
@classmethod
|
|
def _start_flask_server(cls):
|
|
"""Start a Flask server for URL testing."""
|
|
import urllib.request
|
|
import urllib.error
|
|
from werkzeug.serving import make_server
|
|
|
|
cls.flask_app = Flask(__name__)
|
|
|
|
@cls.flask_app.route('/test.jpg')
|
|
def serve_test_image():
|
|
return send_file(cls.jpg_path, mimetype='image/jpeg')
|
|
|
|
@cls.flask_app.route('/health')
|
|
def health_check():
|
|
return 'OK', 200
|
|
|
|
# Bind to an ephemeral port so concurrent/leftover test runs can't clash
|
|
cls.flask_server = make_server('127.0.0.1', 0, cls.flask_app, threaded=True)
|
|
cls.flask_port = cls.flask_server.server_port
|
|
cls.flask_server_running = True
|
|
|
|
cls.flask_thread = threading.Thread(target=cls.flask_server.serve_forever, daemon=True)
|
|
cls.flask_thread.start()
|
|
|
|
# Wait for server to be ready with health check.
|
|
# Generous, because this now raises rather than falling through
|
|
# silently: on a loaded CI runner the accept loop can take several
|
|
# seconds to get scheduled, and a spurious failure here is worse than
|
|
# a slow one. The loop exits as soon as the server answers.
|
|
max_wait = 30
|
|
wait_interval = 0.1 # Check every 100ms
|
|
elapsed = 0
|
|
|
|
while elapsed < max_wait:
|
|
try:
|
|
with urllib.request.urlopen(f'http://127.0.0.1:{cls.flask_port}/health', timeout=1) as response:
|
|
if response.status == 200:
|
|
return
|
|
except (urllib.error.URLError, ConnectionRefusedError, OSError):
|
|
pass
|
|
time.sleep(wait_interval)
|
|
elapsed += wait_interval
|
|
|
|
raise RuntimeError(
|
|
f"Test Flask server did not become ready on port {cls.flask_port} within {max_wait}s")
|
|
|
|
def test_image_url_detection(self):
|
|
"""Test URL detection functionality."""
|
|
img = Image()
|
|
|
|
# Test URL detection
|
|
self.assertTrue(img._is_url("http://example.com/image.jpg"))
|
|
self.assertTrue(img._is_url("https://example.com/image.png"))
|
|
self.assertTrue(img._is_url("ftp://example.com/image.gif"))
|
|
|
|
# Test non-URL detection
|
|
self.assertFalse(img._is_url("image.jpg"))
|
|
self.assertFalse(img._is_url("/path/to/image.png"))
|
|
self.assertFalse(img._is_url("../relative/path.gif"))
|
|
self.assertFalse(img._is_url(""))
|
|
|
|
def test_load_local_image_jpg(self):
|
|
"""Test loading local JPG image."""
|
|
img = Image(self.jpg_path)
|
|
|
|
file_path, pil_img = img.load_image_data()
|
|
|
|
self.assertIsNotNone(pil_img)
|
|
self.assertEqual(file_path, self.jpg_path)
|
|
self.assertEqual(pil_img.size, self.original_size)
|
|
self.assertEqual(img.width, self.original_size[0])
|
|
self.assertEqual(img.height, self.original_size[1])
|
|
|
|
def test_load_local_image_png(self):
|
|
"""Test loading local PNG image."""
|
|
img = Image(self.png_path)
|
|
|
|
file_path, pil_img = img.load_image_data()
|
|
|
|
self.assertIsNotNone(pil_img)
|
|
self.assertEqual(file_path, self.png_path)
|
|
self.assertEqual(pil_img.size, self.original_size)
|
|
|
|
def test_load_local_image_bmp(self):
|
|
"""Test loading local BMP image."""
|
|
img = Image(self.bmp_path)
|
|
|
|
file_path, pil_img = img.load_image_data()
|
|
|
|
self.assertIsNotNone(pil_img)
|
|
self.assertEqual(file_path, self.bmp_path)
|
|
self.assertEqual(pil_img.size, self.original_size)
|
|
|
|
def test_load_local_image_gif(self):
|
|
"""Test loading local GIF image."""
|
|
img = Image(self.gif_path)
|
|
|
|
file_path, pil_img = img.load_image_data()
|
|
|
|
self.assertIsNotNone(pil_img)
|
|
self.assertEqual(file_path, self.gif_path)
|
|
self.assertEqual(pil_img.size, self.original_size)
|
|
|
|
def test_load_nonexistent_image(self):
|
|
"""Test loading non-existent image."""
|
|
img = Image("nonexistent.jpg")
|
|
|
|
file_path, pil_img = img.load_image_data()
|
|
|
|
self.assertIsNone(pil_img)
|
|
self.assertIsNone(file_path)
|
|
|
|
def test_load_empty_source(self):
|
|
"""Test loading with empty source."""
|
|
img = Image("")
|
|
|
|
file_path, pil_img = img.load_image_data()
|
|
|
|
self.assertIsNone(pil_img)
|
|
self.assertIsNone(file_path)
|
|
|
|
def test_auto_update_dimensions(self):
|
|
"""Test automatic dimension updating."""
|
|
img = Image(self.jpg_path, width=50, height=50) # Wrong initial dimensions
|
|
|
|
# Test with auto-update enabled (default)
|
|
file_path, pil_img = img.load_image_data(auto_update_dimensions=True)
|
|
|
|
self.assertEqual(img.width, self.original_size[0])
|
|
self.assertEqual(img.height, self.original_size[1])
|
|
|
|
def test_no_auto_update_dimensions(self):
|
|
"""Test loading without automatic dimension updating."""
|
|
original_width, original_height = 50, 50
|
|
img = Image(self.jpg_path, width=original_width, height=original_height)
|
|
|
|
# Test with auto-update disabled
|
|
file_path, pil_img = img.load_image_data(auto_update_dimensions=False)
|
|
|
|
self.assertEqual(img.width, original_width) # Should remain unchanged
|
|
self.assertEqual(img.height, original_height) # Should remain unchanged
|
|
|
|
def test_get_image_info(self):
|
|
"""Test getting detailed image information."""
|
|
img = Image(self.jpg_path)
|
|
|
|
info = img.get_image_info()
|
|
|
|
self.assertIsInstance(info, dict)
|
|
self.assertIn('format', info)
|
|
self.assertIn('mode', info)
|
|
self.assertIn('size', info)
|
|
self.assertIn('width', info)
|
|
self.assertIn('height', info)
|
|
|
|
self.assertEqual(info['size'], self.original_size)
|
|
self.assertEqual(info['width'], self.original_size[0])
|
|
self.assertEqual(info['height'], self.original_size[1])
|
|
|
|
def test_get_image_info_different_formats(self):
|
|
"""Test getting image info for different formats."""
|
|
formats_and_paths = [
|
|
('JPEG', self.jpg_path),
|
|
('PNG', self.png_path),
|
|
('BMP', self.bmp_path),
|
|
('GIF', self.gif_path),
|
|
]
|
|
|
|
for expected_format, path in formats_and_paths:
|
|
with self.subTest(format=expected_format):
|
|
img = Image(path)
|
|
info = img.get_image_info()
|
|
|
|
self.assertEqual(info['format'], expected_format)
|
|
self.assertEqual(info['size'], self.original_size)
|
|
|
|
def test_get_image_info_nonexistent(self):
|
|
"""Test getting image info for non-existent image."""
|
|
img = Image("nonexistent.jpg")
|
|
|
|
info = img.get_image_info()
|
|
|
|
self.assertEqual(info, {})
|
|
|
|
@unittest.skipUnless(FLASK_AVAILABLE, "Flask not available for URL testing")
|
|
def test_load_image_from_url(self):
|
|
"""Test loading image from URL."""
|
|
url = f"http://127.0.0.1:{self.flask_port}/test.jpg"
|
|
img = Image(url)
|
|
|
|
file_path, pil_img = img.load_image_data()
|
|
|
|
self.assertIsNotNone(pil_img)
|
|
self.assertIsNotNone(file_path)
|
|
self.assertTrue(file_path.endswith('.tmp')) # Should be a temp file
|
|
self.assertEqual(pil_img.size, self.original_size)
|
|
|
|
# Check that dimensions were updated
|
|
self.assertEqual(img.width, self.original_size[0])
|
|
self.assertEqual(img.height, self.original_size[1])
|
|
|
|
@unittest.skipUnless(FLASK_AVAILABLE, "Flask not available for URL testing")
|
|
def test_get_image_info_from_url(self):
|
|
"""Test getting image info from URL."""
|
|
url = f"http://127.0.0.1:{self.flask_port}/test.jpg"
|
|
img = Image(url)
|
|
|
|
info = img.get_image_info()
|
|
|
|
self.assertIsInstance(info, dict)
|
|
self.assertEqual(info['format'], 'JPEG')
|
|
self.assertEqual(info['size'], self.original_size)
|
|
|
|
def test_load_invalid_url(self):
|
|
"""Test loading from invalid URL."""
|
|
img = Image("http://nonexistent.domain/image.jpg")
|
|
|
|
file_path, pil_img = img.load_image_data()
|
|
|
|
self.assertIsNone(pil_img)
|
|
self.assertIsNone(file_path)
|
|
|
|
def test_multiple_loads_cleanup(self):
|
|
"""Test that multiple loads don't leave temp files."""
|
|
img = Image(self.jpg_path)
|
|
|
|
# Load multiple times
|
|
for _ in range(3):
|
|
file_path, pil_img = img.load_image_data()
|
|
self.assertIsNotNone(pil_img)
|
|
|
|
def test_original_sample_image(self):
|
|
"""Test loading the original sample image if it exists."""
|
|
if os.path.exists(self.sample_image_path):
|
|
img = Image(self.sample_image_path)
|
|
|
|
file_path, pil_img = img.load_image_data()
|
|
|
|
self.assertIsNotNone(pil_img)
|
|
self.assertEqual(file_path, self.sample_image_path)
|
|
|
|
# Test that we can get image info
|
|
info = img.get_image_info()
|
|
self.assertIsInstance(info, dict)
|
|
self.assertIn('format', info)
|
|
self.assertIn('size', info)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|