Add favorites feature with toggle filters, group management, color/icon pickers, dashboard widget, and star buttons across containers/docker sections with persistent storage and real-time UI updates
Tests / Backend Tests (Python) (3.10) (push) Has been cancelled
Tests / Backend Tests (Python) (3.11) (push) Has been cancelled
Tests / Backend Tests (Python) (3.12) (push) Has been cancelled
Tests / Frontend Tests (JS) (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / All Tests Passed (push) Has been cancelled
Tests / Backend Tests (Python) (3.10) (push) Has been cancelled
Tests / Backend Tests (Python) (3.11) (push) Has been cancelled
Tests / Backend Tests (Python) (3.12) (push) Has been cancelled
Tests / Frontend Tests (JS) (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / All Tests Passed (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_favorite_groups_crud(client, host_factory, db_session):
|
||||
# Create group
|
||||
resp = await client.post(
|
||||
"/api/favorites/groups",
|
||||
json={"name": "Production", "sort_order": 1, "color": "purple"},
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
group = resp.json()
|
||||
assert group["name"] == "Production"
|
||||
|
||||
# List groups
|
||||
resp = await client.get("/api/favorites/groups")
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert any(g["name"] == "Production" for g in data["groups"])
|
||||
|
||||
# Update group
|
||||
resp = await client.patch(
|
||||
f"/api/favorites/groups/{group['id']}",
|
||||
json={"name": "Prod", "sort_order": 2},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
updated = resp.json()
|
||||
assert updated["name"] == "Prod"
|
||||
assert updated["sort_order"] == 2
|
||||
|
||||
# Delete group
|
||||
resp = await client.delete(f"/api/favorites/groups/{group['id']}")
|
||||
assert resp.status_code == 204
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_favorite_containers_add_list_delete(client, host_factory, db_session):
|
||||
# Create a host + docker container in DB
|
||||
host = await host_factory.create(db_session, id="host-0001", name="docker1", ip_address="10.0.0.10")
|
||||
|
||||
from app.crud.docker_container import DockerContainerRepository
|
||||
|
||||
repo = DockerContainerRepository(db_session)
|
||||
c = await repo.upsert(
|
||||
host_id=host.id,
|
||||
container_id="abcdef123456",
|
||||
name="portainer",
|
||||
image="portainer/portainer-ce:latest",
|
||||
state="running",
|
||||
status="Up 1 hour",
|
||||
health=None,
|
||||
ports={"raw": "0.0.0.0:9000->9000/tcp"},
|
||||
labels=None,
|
||||
compose_project=None,
|
||||
)
|
||||
await db_session.commit()
|
||||
|
||||
# Create favorite
|
||||
resp = await client.post(
|
||||
"/api/favorites/containers",
|
||||
json={"host_id": host.id, "container_id": c.container_id, "group_id": None},
|
||||
)
|
||||
assert resp.status_code == 201
|
||||
fav = resp.json()
|
||||
assert fav["docker_container"]["name"] == "portainer"
|
||||
fav_id = fav["id"]
|
||||
|
||||
# List favorites
|
||||
resp = await client.get("/api/favorites/containers")
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert any(x["id"] == fav_id for x in data["containers"])
|
||||
|
||||
# Idempotent add should return 201? We return 201 always, but route returns 201; if exists we return 200? current impl returns 201 for POST but returns existing directly.
|
||||
resp2 = await client.post(
|
||||
"/api/favorites/containers",
|
||||
json={"host_id": host.id, "container_id": c.container_id, "group_id": None},
|
||||
)
|
||||
assert resp2.status_code in (200, 201)
|
||||
fav2 = resp2.json()
|
||||
assert fav2["id"] == fav_id
|
||||
|
||||
# Delete favorite
|
||||
resp = await client.delete(f"/api/favorites/containers/{fav_id}")
|
||||
assert resp.status_code == 204
|
||||
|
||||
# List favorites -> empty
|
||||
resp = await client.get("/api/favorites/containers")
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["containers"] == []
|
||||
@@ -638,3 +638,52 @@ describe('DashboardCore - Constructor', () => {
|
||||
expect(core.apiBase).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// TESTS: Dashboard UI helpers (main.js) - Ad-Hoc widget regression
|
||||
// ============================================================================
|
||||
|
||||
describe('main.js - Ad-Hoc widget regression', () => {
|
||||
it('computes counters from task log status and shows load more when more items exist locally', async () => {
|
||||
// Minimal DOM for widget
|
||||
document.body.innerHTML = `
|
||||
<div id="adhoc-widget-history"></div>
|
||||
<button id="adhoc-widget-load-more" class="hidden"></button>
|
||||
<div id="adhoc-widget-success"></div>
|
||||
<div id="adhoc-widget-failed"></div>
|
||||
<div id="adhoc-widget-total"></div>
|
||||
<div id="adhoc-widget-count"></div>
|
||||
`;
|
||||
|
||||
// Import side-effects are heavy; instead, instantiate the real DashboardManager by requiring main.js.
|
||||
// main.js defines DashboardManager on the global scope.
|
||||
await import('../../app/main.js');
|
||||
|
||||
// Create instance and stub required methods used by widget
|
||||
const dash = new window.DashboardManager();
|
||||
dash.escapeHtml = (s) => String(s ?? '');
|
||||
dash.formatTimeAgo = () => 'À l\'instant';
|
||||
dash.viewTaskLogContent = vi.fn();
|
||||
dash.showAdHocConsole = vi.fn();
|
||||
|
||||
// Emulate loaded adhoc task logs
|
||||
dash.adhocWidgetLimit = 2;
|
||||
dash.adhocWidgetOffset = 0;
|
||||
dash.adhocWidgetLogs = [
|
||||
{ id: 'l1', status: 'completed', task_name: 'Ad-hoc: docker version', target: 'h1', created_at: new Date().toISOString(), duration_seconds: 1.2 },
|
||||
{ id: 'l2', status: 'failed', task_name: 'Ad-hoc: docker ps', target: 'h2', created_at: new Date().toISOString(), duration_seconds: 2.3 },
|
||||
{ id: 'l3', status: 'completed', task_name: 'Ad-hoc: cat /etc/os-release', target: 'h3', created_at: new Date().toISOString(), duration_seconds: 0.8 },
|
||||
];
|
||||
dash.adhocWidgetTotalCount = 3;
|
||||
dash.adhocWidgetHasMore = false;
|
||||
|
||||
dash.renderAdhocWidget();
|
||||
|
||||
expect(document.getElementById('adhoc-widget-success').textContent).toBe('2');
|
||||
expect(document.getElementById('adhoc-widget-failed').textContent).toBe('1');
|
||||
expect(document.getElementById('adhoc-widget-total').textContent).toBe('3');
|
||||
expect(document.getElementById('adhoc-widget-count').textContent).toContain('3');
|
||||
// Since only 2 are displayed but 3 are loaded, load-more must be visible.
|
||||
expect(document.getElementById('adhoc-widget-load-more').classList.contains('hidden')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user