Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
122 commits
Select commit Hold shift + click to select a range
c1ac41c
Initial plan
Copilot Dec 26, 2025
ace4171
Add Python modules: db_update, file_handler, library_handler, tttool_…
Copilot Dec 26, 2025
6bdc2c5
Add Flask-based main application (ttmp32gme.py)
Copilot Dec 26, 2025
5517efa
Fix global variable declarations in Flask routes
Copilot Dec 26, 2025
8e0ecaa
Update README with Python backend installation and usage instructions
Copilot Dec 26, 2025
6060cb7
Fix typo and tool name in comments per code review
Copilot Dec 26, 2025
11f22fe
Implement TODOs: dynamic file serving, versioning, and code cleanup
Copilot Dec 26, 2025
4cd9ec7
Add comprehensive test suite: unit tests, E2E tests, and CI workflows
Copilot Dec 26, 2025
4919dd6
Fix code review issues: update ChromeDriver installation and exclude …
Copilot Dec 26, 2025
bf6fdcc
Add explicit permissions to GitHub Actions workflows for security
Copilot Dec 26, 2025
4e85bc3
Add uv support, fix tttool installation, and comprehensive E2E tests …
Copilot Dec 26, 2025
7a34f63
Address code review: fix imports, remove duplication, improve error h…
Copilot Dec 26, 2025
e1e17ca
Fix tttool installation URL, argparse -h conflict, and remove obsolet…
Copilot Dec 26, 2025
5ad6a37
Code review fixes: remove redundant type=str and fix shell exit syntax
Copilot Dec 26, 2025
b0234e2
Fix CI failures: extract tttool to subdirectory and include config.sq…
Copilot Dec 26, 2025
576e4cf
Remove trailing blank lines from MANIFEST.in
Copilot Dec 26, 2025
112ca9c
Fix integration tests: update URLs to match Python Flask routes
Copilot Dec 26, 2025
7b0865b
Fix tttool cleanup: use mktemp and chmod before removal
Copilot Dec 26, 2025
02e3e31
Fix E2E tests: convert templates to Jinja2 and update test selectors
Copilot Dec 26, 2025
d5b8f57
Fix bare except clauses in E2E tests per code review
Copilot Dec 26, 2025
45f5385
Comprehensive E2E test improvements: consolidate fixtures, add tttool…
Copilot Dec 26, 2025
4aadb3e
Add local E2E test script and fix tttool tests location and imports
Copilot Dec 26, 2025
a6c9e5b
Fix E2E test script: ensure jq is installed for ChromeDriver detection
Copilot Dec 26, 2025
7f6767f
Fix E2E test script: use system ChromeDriver and add robust uv/pip fa…
Copilot Dec 26, 2025
bb10e44
Redirect pip install output to file to keep script output concise
Copilot Dec 26, 2025
168f8b2
Add test selection arguments to E2E script for targeted test execution
Copilot Dec 26, 2025
a704420
Fix E2E upload test: add upload button click and comprehensive debugging
Copilot Dec 26, 2025
8bc940c
Enhance script: redirect all install output to log, kill existing ser…
Copilot Dec 26, 2025
d67106a
Fix test fixture: improve error handling for MP3 tag creation
Copilot Dec 26, 2025
87134a1
Fix server port conflict: E2E tests now use script-started server on …
Copilot Dec 26, 2025
96cbb8a
Convert test_audio_files to context manager with proper cleanup and r…
Copilot Dec 26, 2025
a0e60a0
Fix upload handler: ensure album_list has enough elements before inde…
Copilot Dec 26, 2025
81f9953
Fix test fixture: use EasyMP3 for ID3 tags compatible with library_ha…
Copilot Dec 26, 2025
74f5911
Fix EasyID3 import: use MP3(file, ID3=EasyID3) instead of EasyMP3
Copilot Dec 26, 2025
58bcaa3
Fix library_handler: use EasyID3 for MP3 metadata extraction
Copilot Dec 26, 2025
bbd3454
Add comprehensive debug logging to trace album creation process
Copilot Dec 26, 2025
15fbe42
Fix E2E test: wait for AJAX-loaded albums to appear in library
Copilot Dec 26, 2025
531d344
Fix upload test: manually navigate to library if automatic redirect d…
Copilot Dec 26, 2025
a426d72
Fix E2E tests: use correct database filename config.sqlite instead of…
Copilot Dec 26, 2025
3fa1efb
Update run_e2e_tests_locally.sh
thawn Dec 26, 2025
47fcfab
fix selenium issue in e2e tests
thawn Dec 28, 2025
9e3df7c
add uv lock file
thawn Dec 28, 2025
bb5f18b
adapt javascript to jinja template library used by flask
thawn Dec 28, 2025
212f682
fix conflict between static assets/ and dynamic oid and album image p…
thawn Dec 28, 2025
515dd2e
adapt tests to new image path
thawn Dec 28, 2025
e41db53
changed html templates to use Jinja2 syntax for variable replacement
thawn Dec 28, 2025
e6a36f7
apply Jinja2 safe filter to template variables for proper HTML rendering
thawn Dec 28, 2025
5105100
remove any non letter or number character to clean up filenames.
thawn Dec 28, 2025
869ff76
convert configuration strings to numeric types
thawn Dec 28, 2025
64b6b0b
refactor print layout generation to use Jinja2 template for album ren…
thawn Dec 28, 2025
f38d21f
make sure albums are always formatted the same
thawn Dec 28, 2025
4aff0b2
removed obsolete check code
thawn Dec 28, 2025
85654cb
fix album cover images are 'null'
thawn Dec 28, 2025
8da4330
improve code generation tests
thawn Dec 29, 2025
f7b94d8
fix: move audio files to the album after upload
thawn Dec 29, 2025
2e81a0e
fix: when moving the library, also change library path in the database
thawn Dec 29, 2025
66239a9
fix: fix mp3 to ogg track conversion and last used code default value
thawn Dec 29, 2025
3fcaf81
fix: allow periods in filenames to avoid messing up file extensions
thawn Dec 29, 2025
992ce82
copy then deltete library instead of moving it to allow rollback in c…
thawn Dec 29, 2025
2af010b
improve tests
thawn Dec 29, 2025
2fdb123
add todos
thawn Dec 29, 2025
44a31a3
apply black formatter
thawn Dec 29, 2025
85ce6cc
revert to using Google Chrome in CI e2e test setup
thawn Dec 29, 2025
bb40f34
Add command to start web service in E2E tests
thawn Dec 29, 2025
088d31f
start ttmp23gme service in background
thawn Dec 29, 2025
27090c6
Implement function to open library element for editing
thawn Dec 29, 2025
4d33363
fix GME creation in comprehensive test
thawn Dec 29, 2025
1902ce2
fix syntax error
thawn Dec 29, 2025
e1981ee
Update E2E tests workflow to fail on errors
thawn Dec 29, 2025
9eef090
Implement TransientConfigChange for audio format
thawn Dec 29, 2025
3cd87f4
Refactor audio format config change handling
thawn Dec 29, 2025
8b1f0a5
Fix syntax error in _change_config method
thawn Dec 29, 2025
21763ba
Fix syntax error in audio format toggle logic
thawn Dec 29, 2025
a7aca99
Fix constructor method in TransientConfigChange class
thawn Dec 29, 2025
7bd548a
Fix SQL query parameter in test_comprehensive.py
thawn Dec 29, 2025
b998724
Fix method definitions to include 'self' parameter
thawn Dec 29, 2025
59796a1
Fix SQL query parameter in TestWebInterface class
thawn Dec 29, 2025
9a8f3e7
always triggers E2E tests workflow
thawn Dec 29, 2025
d335059
fix e2e test test_edit_album_info
thawn Dec 29, 2025
1028ff3
fix: make update_album function use old_oid for database updates
thawn Dec 29, 2025
988310a
fix: update album entry using old_oid for database updates
thawn Dec 29, 2025
0a722e4
refactor: remove get_album_online function and update references to g…
thawn Dec 29, 2025
4c988e6
refactor: rename update method to update_db for clarity
thawn Dec 29, 2025
9d59b94
refactor: remove get_album_online function and update references to g…
thawn Dec 29, 2025
649f982
fix: get select/deselect all checkbox test to work
thawn Dec 29, 2025
2de38d8
fix: handle missing uid gracefully in album update
thawn Dec 29, 2025
2582705
feat: implement track update and extraction functions in DB and libra…
thawn Dec 29, 2025
ddcc042
refactor: prevent misidentification of fixture test_audio_files_conte…
thawn Dec 29, 2025
7986fcf
fix: update select/deselect all checkbox handling to use find_element…
thawn Dec 29, 2025
8a318f1
test: skip print album test until test functionality is properly impl…
thawn Dec 29, 2025
9961a8f
update todo in readme
thawn Dec 29, 2025
02a4f0e
removed obsolete todo item in README
thawn Dec 29, 2025
6f8339b
update todo in readme
thawn Dec 29, 2025
5b88605
Implement test_print_album: select albums, navigate to print page, an…
Copilot Dec 30, 2025
17ba15f
Refactor: Migrate to DBHandler in ttmp32gme.py, print_handler.py, and…
Copilot Dec 30, 2025
b1cb8cf
Complete migration: Update tttool_handler.py to use DBHandler and rem…
Copilot Dec 30, 2025
69f1e67
Refactor: Integrate db_update.py functionality into DBHandler.update_…
Copilot Dec 30, 2025
f55d559
Fix: Use existing DBHandler.update_db() method without parameters as …
Copilot Dec 30, 2025
0a6d256
Fix SQLite threading issue: use check_same_thread=False for Flask mul…
Copilot Dec 30, 2025
c5870ed
Refactor: Replace db.cursor() pattern with DBHandler.execute() method…
Copilot Dec 30, 2025
03f9949
fix: Use standalone extract_tracks_from_album function instead of met…
thawn Dec 30, 2025
5047db6
fix: Update serve_dynamic_image to fetch row with improved syntax
thawn Dec 30, 2025
4bdfa5f
fixed db_handler migration issues in tttool_handler
thawn Dec 30, 2025
59f3e16
fix: properly move audio files to library
thawn Dec 30, 2025
aa39239
fix test_tttool_handler
thawn Dec 30, 2025
ab7695c
fix: update file path handling in convert_tracks and make_gme functions
thawn Dec 30, 2025
cc0bb21
feat: make e2e tests fail faster by lowering timeouts
thawn Dec 30, 2025
cd902b9
fix: improve layout verification in e2e test_print_album
thawn Dec 30, 2025
f591129
remove done items in ToDo list in README
thawn Dec 30, 2025
7761467
Refactor: Split E2E test script into setup and run scripts for better…
Copilot Dec 30, 2025
82e3b49
Add Pydantic validation for frontend input with models in db_handler.py
Copilot Dec 30, 2025
48fffdb
updated uv.lock with added pydantic dependency
thawn Dec 30, 2025
6999b3b
fix: player mode validation
thawn Dec 30, 2025
78852d9
apply black formatter with standard options
thawn Dec 30, 2025
291b46f
updated todos in readme
thawn Dec 30, 2025
b4893cd
Add comprehensive Copilot coding agent instructions for repository on…
Copilot Dec 30, 2025
7fc8d0c
remove deprecated code
thawn Dec 30, 2025
26e4e78
verified and updated copilot instructions
thawn Dec 30, 2025
5f506a5
deleted obsolete tests
thawn Dec 30, 2025
c75df99
Add copilot-setup-steps.yml workflow for Copilot coding agent environ…
Copilot Dec 30, 2025
795f3f0
fix: updated copilot setup steps to match template
thawn Dec 30, 2025
df4c09b
updated copilot-instructions to take into account that environment is…
thawn Dec 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
name: End-to-End Tests

# This workflow requires manual approval to run
on:
workflow_dispatch:
inputs:
reason:
description: 'Reason for running E2E tests'
required: false
default: 'Manual E2E test run'
pull_request:
types: [labeled]

permissions:
contents: read
pull-requests: write # Needed for posting comments on PRs

# Only run when 'run-e2e-tests' label is added or workflow is manually dispatched
jobs:
check-label:
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
should-run: ${{ steps.check.outputs.result }}
steps:
- id: check
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
echo "result=true" >> $GITHUB_OUTPUT
elif [[ "${{ github.event.label.name }}" == "run-e2e-tests" ]]; then
echo "result=true" >> $GITHUB_OUTPUT
else
echo "result=false" >> $GITHUB_OUTPUT
fi

e2e-tests:
name: End-to-End Tests with Selenium
runs-on: ubuntu-latest
needs: check-label
if: needs.check-label.outputs.should-run == 'true'

permissions:
contents: read
pull-requests: write # Needed for posting comments

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Needed for setuptools-scm

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y wget unzip

- name: Install Chrome and ChromeDriver
run: |
# Install Chrome
wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo dpkg -i google-chrome-stable_current_amd64.deb || sudo apt-get -f install -y

# Install ChromeDriver using Chrome for Testing
CHROME_VERSION=$(google-chrome --version | awk '{print $3}' | cut -d '.' -f 1)
CHROMEDRIVER_URL=$(curl -s "https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json" | \
grep -o '"chromedriver".*"linux64".*"url":"[^"]*' | \
grep -o 'https://[^"]*' | head -1)
wget -q "$CHROMEDRIVER_URL" -O chromedriver-linux64.zip
unzip chromedriver-linux64.zip
sudo mv chromedriver-linux64/chromedriver /usr/local/bin/
sudo chmod +x /usr/local/bin/chromedriver
rm -rf chromedriver-linux64*

- name: Install tttool (static build)
run: |
wget -q https://github.com/entropia/tip-toi-reveng/releases/download/latest/tttool-Linux
chmod +x tttool-Linux
sudo mv tttool-Linux /usr/local/bin/tttool

- name: Install ffmpeg (static build)
run: |
wget -q https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz
tar -xf ffmpeg-release-amd64-static.tar.xz
sudo mv ffmpeg-*-amd64-static/ffmpeg /usr/local/bin/
sudo mv ffmpeg-*-amd64-static/ffprobe /usr/local/bin/
rm -rf ffmpeg-*-amd64-static*

- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[test]"

- name: Download test audio files (public domain)
run: |
mkdir -p tests/fixtures
# Download small public domain audio files for testing
# Using a very short silent MP3 file (can be replaced with actual test files)
wget -q -O tests/fixtures/test1.mp3 "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3" || echo "Warning: Could not download test file"

- name: Run E2E tests with pytest
run: |
pytest tests/e2e/ -v --tb=short -m "e2e" --html=e2e-report.html --self-contained-html
continue-on-error: true

- name: Upload test report
uses: actions/upload-artifact@v4
with:
name: e2e-test-report
path: e2e-report.html
if: always()

- name: Comment PR with results
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need the step please remove it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in commit 45f5385. The PR comment step has been deleted from the E2E workflow.

if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '✅ End-to-end tests completed! Check the artifacts for the detailed report.'
});
64 changes: 47 additions & 17 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,17 @@ on:
pull_request:
branches: [ main, master, develop ]

permissions:
contents: read

jobs:
test:
unit-tests:
name: Unit Tests
runs-on: ubuntu-latest

permissions:
contents: read

strategy:
matrix:
python-version: ['3.11', '3.12', '3.13']
Expand All @@ -22,32 +29,55 @@ jobs:
with:
python-version: ${{ matrix.python-version }}

- name: Set up Perl
uses: shogo82148/actions-setup-perl@v1
with:
perl-version: '5.38'
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[test]"

- name: Install Perl dependencies
- name: Run unit tests with pytest
run: |
cpanm --notest EV AnyEvent::HTTPD Path::Class JSON::XS URI::Escape
pytest tests/unit/ -v --tb=short --html=unit-report.html --self-contained-html

- name: Upload test report
uses: actions/upload-artifact@v4
with:
name: unit-test-report-${{ matrix.python-version }}
path: unit-report.html
if: always()

integration-tests:
name: Integration Tests
runs-on: ubuntu-latest

permissions:
contents: read

strategy:
matrix:
python-version: ['3.11', '3.12', '3.13']

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install -e .
pip install -e ".[test]"

- name: Start ttmp32gme server
- name: Start ttmp32gme Python server
run: |
cd src
perl ttmp32gme.pl &
python -m ttmp32gme.ttmp32gme --port 10020 &
echo $! > /tmp/ttmp32gme.pid
sleep 5
env:
TTMP32GME_PORT: 10020

- name: Run tests with pytest
- name: Run integration tests with pytest
run: |
pytest tests/ -v --tb=short --html=report.html --self-contained-html
pytest tests/test_web_frontend.py -v --tb=short --html=integration-report.html --self-contained-html
env:
TTMP32GME_URL: http://localhost:10020

Expand All @@ -61,6 +91,6 @@ jobs:
- name: Upload test report
uses: actions/upload-artifact@v4
with:
name: pytest-report-${{ matrix.python-version }}
path: report.html
name: integration-test-report-${{ matrix.python-version }}
path: integration-report.html
if: always()
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@ __pycache__/
*.egg-info/
report.html
assets/
src/ttmp32gme/_version.py
19 changes: 17 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "ttmp32gme"
version = "1.0.1"
dynamic = ["version"]
description = "TipToi MP3 to GME converter - Python backend"
requires-python = ">=3.11"
readme = "README.md"
Expand All @@ -19,11 +19,26 @@ dependencies = [
test = [
"pytest>=7.4.0",
"pytest-html>=4.0.0",
"selenium>=4.15.0",
]

[project.scripts]
ttmp32gme = "ttmp32gme.ttmp32gme:main"

[build-system]
requires = ["setuptools>=61.0"]
requires = ["setuptools>=61.0", "setuptools-scm>=8.0"]
build-backend = "setuptools.build_meta"

[tool.setuptools_scm]
version_file = "src/ttmp32gme/_version.py"

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = "test_*.py"
python_classes = "Test*"
python_functions = "test_*"
markers = [
"e2e: End-to-end tests using Selenium (deselect with '-m \"not e2e\"')",
"slow: Slow tests (deselect with '-m \"not slow\"')",
]
addopts = "-v --strict-markers"
5 changes: 4 additions & 1 deletion src/ttmp32gme/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
"""ttmp32gme - TipToi MP3 to GME converter."""

__version__ = "1.0.1"
try:
from ._version import version as __version__
except ImportError:
__version__ = "0.0.0+unknown"
14 changes: 8 additions & 6 deletions src/ttmp32gme/library_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ def get_album_online(oid: int, httpd, connection) -> Dict[str, Any]:

Args:
oid: Album OID
httpd: HTTP server instance
httpd: HTTP server instance (not used in Flask - routes handle this)
connection: Database connection

Returns:
Expand All @@ -472,8 +472,9 @@ def get_album_online(oid: int, httpd, connection) -> Dict[str, Any]:
if not album:
return {}

# TODO: Register album files with HTTP server
# This would involve adding routes for cover images and other assets
# Files are served automatically via Flask routes in ttmp32gme.py
# /assets/images/<oid>/<filename> for covers
# /assets/images/<oid_file> for OID codes

return album

Expand All @@ -484,11 +485,12 @@ def put_file_online(file_path: Path, online_path: str, httpd) -> bool:
Args:
file_path: Local file path
online_path: URL path
httpd: HTTP server instance
httpd: HTTP server instance (not used in Flask - routes handle this)

Returns:
True if successful
"""
# TODO: Implement HTTP route registration
# This depends on the web framework being used (Flask, etc.)
# Files are served automatically via Flask routes in ttmp32gme.py
# No dynamic route registration needed - the serve_dynamic_image route
# handles all /assets/images/* requests
return True
12 changes: 5 additions & 7 deletions src/ttmp32gme/print_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ def format_tracks(album: Dict[str, Any], oid_map: Dict[str, Dict],
oid_file = oid_files[0]
oid_path = f'/assets/images/{oid_file.name}'

# Register file with HTTP server (TODO: implement)
# put_file_online(oid_file, oid_path, httpd)
# File is automatically served via Flask route in ttmp32gme.py

content += '<li class="list-group-item">'
content += f'<table width="100%"><tr><td><div class="img-6mm track-img-container">'
Expand Down Expand Up @@ -80,7 +79,7 @@ def format_controls(oid_map: Dict[str, Dict], httpd, connection) -> str:
content = ""
for i, (oid_file, oid, icon) in enumerate(zip(oid_files, oids, icons)):
oid_path = f'/assets/images/{oid_file.name}'
# put_file_online(oid_file, oid_path, httpd)
# File is automatically served via Flask route in ttmp32gme.py
content += template.format(oid_path, oid, icon)

return content
Expand All @@ -106,7 +105,7 @@ def format_track_control(track_no: int, oid_map: Dict[str, Dict],
oid_file = oid_files[0]
oid_path = f'/assets/images/{oid_file.name}'

# put_file_online(oid_file, oid_path, httpd)
# File is automatically served via Flask route in ttmp32gme.py

template = ('<a class="btn btn-default play-control">'
'<img class="img-24mm play-img" src="{}" alt="oid: {}">{}</a>')
Expand All @@ -131,7 +130,7 @@ def format_main_oid(oid: int, oid_map: Dict[str, Dict],
oid_file = oid_files[0]
oid_path = f'/assets/images/{oid_file.name}'

# put_file_online(oid_file, oid_path, httpd)
# File is automatically served via Flask route in ttmp32gme.py

return f'<img class="img-24mm play-img" src="{oid_path}" alt="oid: {oid}">'

Expand Down Expand Up @@ -197,8 +196,7 @@ def create_print_layout(oids: List[int], template, config: Dict[str, Any],
album['main_oid_image'] = format_main_oid(oid, oid_map, httpd, connection)
album['formatted_cover'] = format_cover(album)

# Render template (TODO: implement template rendering)
# For now, just add a simple HTML representation
# Create HTML for album (templates would be better, but this works for now)
content += f'<div class="album" data-oid="{oid}">'
content += f'<h2>{album.get("album_title", "Unknown")}</h2>'
content += album['formatted_cover']
Expand Down
Loading
Loading