Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 27 additions & 0 deletions tests/test_maintenance.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,25 @@ def test_apply_creates_backup_and_replaces_file(tmp_path, monkeypatch):
assert report.results[0].detail["cache_invalidated"] is True


def test_source_failure_preserves_existing_file_and_reports_error(tmp_path, monkeypatch):
current = _sp500_rows(450)
output_path = tmp_path / "data" / "sp500_constituents.csv"
_write_constituents(output_path, current)
_disable_cache_invalidation(monkeypatch)

def failing_source():
raise RuntimeError("source unavailable")

runner = _runner(tmp_path, {"sp500_constituents": failing_source})

report = runner.run(tasks=["sp500_constituents"], dry_run=False)

assert report.status == "failed"
assert "source unavailable" in report.errors
assert _read_symbols(output_path) == [r["symbol"] for r in current]
assert not (tmp_path / "data" / "backups" / "constituents").exists()


def test_validation_failure_preserves_existing_file(tmp_path, monkeypatch):
current = _sp500_rows(450)
invalid = _sp500_rows(1)
Expand Down Expand Up @@ -107,6 +126,14 @@ def test_maintenance_page_loads(client):
assert b"System Maintenance" in response.data


def test_maintenance_nav_link_registered_in_main_js(client):
response = client.get("/static/js/main.js")

assert response.status_code == 200
assert b"/maintenance" in response.data
assert b"Maintenance" in response.data


def test_maintenance_status_api_loads(client):
response = client.get("/api/maintenance/status")

Expand Down
36 changes: 36 additions & 0 deletions web/static/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
document.addEventListener('DOMContentLoaded', () => {
console.log('[TWS Robot] UI ready');

// Ensure optional/operator pages are discoverable even when the server-side
// navbar template has not been expanded yet.
_ensureMaintenanceNavLink();

// Auto-enrich symbol cells with company names
_enrichSymbolNames();
});
Expand Down Expand Up @@ -79,6 +83,38 @@ window.renderMarkdown = function renderMarkdown(text) {
.replace(/\n/g, '<br>');
};

/**
* Add the System Maintenance console to the Monitoring dropdown.
* This keeps the operator-only page discoverable from the web UI.
* @private
*/
function _ensureMaintenanceNavLink() {
const existing = document.querySelector('a[href="/maintenance"]');
if (existing) return;

const monitoringToggle = Array.from(document.querySelectorAll('.nav-dropdown-toggle'))
.find(btn => (btn.textContent || '').includes('Monitoring'));
if (!monitoringToggle) return;

const dropdown = monitoringToggle.closest('.nav-dropdown');
const menu = dropdown ? dropdown.querySelector('.nav-dropdown-menu') : null;
if (!menu) return;

const li = document.createElement('li');
const isMaintenancePage = window.location.pathname === '/maintenance' ||
window.location.pathname.startsWith('/maintenance/');
if (isMaintenancePage) {
li.className = 'active';
dropdown.classList.add('active');
}

const link = document.createElement('a');
link.href = '/maintenance';
link.textContent = 'Maintenance';
li.appendChild(link);
menu.appendChild(li);
}

/**
* Fetch company names for all ``[data-symbol]`` elements on the page
* and enrich them with a tooltip (title) and a small subtitle showing
Expand Down
Loading