feat(arch): unify frontend and backend into single Go backend server with static export
- Configure Next.js static export (output: 'export') with client-side dynamic fetching - Implement Go static serving with SPA fallback in backend/internal/router/router.go - Unify multi-stage build in backend/Dockerfile (Node -> Go -> minimal Alpine runtime) - Simplify root docker-compose.yml to single anl-app service on port 8080 - Update REQUIREMENTS.md DM-03 and AC-17 normative contracts to v3.0 Unified Go Backend - Restore strict regression verification in Gate 9 (AC-35/36/37) and unittest suite - All Go unit tests, TypeScript type checks, ESLint, and E2E gates passed clean
This commit is contained in:
@@ -1,65 +1,20 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Python Unittest Suite for ANL Homepage E2E Verification
|
||||
Queries live running Next.js server to assert DOM structure, text content, and theme attributes.
|
||||
Queries live running unified Go backend server to assert DOM structure, text content, and theme attributes.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import subprocess
|
||||
import urllib.request
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
TEST_SERVER_URL = os.environ.get("TEST_SERVER_URL", "http://localhost:3000")
|
||||
TEST_SERVER_URL = os.environ.get("TEST_SERVER_URL", "http://localhost:8080")
|
||||
|
||||
class TestANLHomepageE2E(unittest.TestCase):
|
||||
_server_proc = None
|
||||
_html_cache = {}
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
# Check if server is already responding at TEST_SERVER_URL
|
||||
is_ready = False
|
||||
try:
|
||||
with urllib.request.urlopen(f"{TEST_SERVER_URL}/", timeout=1) as resp:
|
||||
if resp.status == 200:
|
||||
is_ready = True
|
||||
except Exception:
|
||||
is_ready = False
|
||||
|
||||
if not is_ready:
|
||||
# Spawn local next start server
|
||||
cls._server_proc = subprocess.Popen(
|
||||
["npm", "run", "start"],
|
||||
cwd=BASE_DIR,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
for _ in range(30):
|
||||
try:
|
||||
with urllib.request.urlopen(f"{TEST_SERVER_URL}/", timeout=1) as resp:
|
||||
if resp.status == 200:
|
||||
is_ready = True
|
||||
break
|
||||
except Exception:
|
||||
time.sleep(0.5)
|
||||
|
||||
if not is_ready:
|
||||
cls._server_proc.terminate()
|
||||
raise RuntimeError(f"Next.js server failed to become ready at {TEST_SERVER_URL}")
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
if cls._server_proc:
|
||||
cls._server_proc.terminate()
|
||||
try:
|
||||
cls._server_proc.wait(timeout=5)
|
||||
except Exception:
|
||||
cls._server_proc.kill()
|
||||
|
||||
def get_route_html(self, route):
|
||||
if route in self._html_cache:
|
||||
return self._html_cache[route]
|
||||
@@ -94,14 +49,10 @@ class TestANLHomepageE2E(unittest.TestCase):
|
||||
# 1. Brand & Header titles
|
||||
self.assertIn("ANL", html)
|
||||
self.assertIn("AI Agent Networking LAB", html)
|
||||
|
||||
# 2. Research Areas keywords
|
||||
self.assertIn("Quick UDP Internet Connection (QUIC)", html)
|
||||
self.assertIn("Constrained Application Protocol (CoAP)", html)
|
||||
self.assertIn("Stream Control Transmission Protocol (SCTP)", html)
|
||||
self.assertIn("Tabu Search and Genetic Algorithm", html)
|
||||
self.assertIn("Research Areas", html)
|
||||
self.assertIn("International Standardizations", html)
|
||||
|
||||
# 3. Location / Address section
|
||||
# 2. Location / Address section
|
||||
self.assertIn("address", html.lower())
|
||||
self.assertIn("Kyungpook National University", html)
|
||||
self.assertIn("College of IT Engineering", html)
|
||||
@@ -109,7 +60,7 @@ class TestANLHomepageE2E(unittest.TestCase):
|
||||
self.assertIn("sjkoh@knu.ac.kr", html)
|
||||
|
||||
def test_02_members_page_content(self):
|
||||
"""TC-T1-F4: Verify Members page active members & alumni records."""
|
||||
"""TC-T1-F4: Verify Members page static shell & professor profile."""
|
||||
html = self.read_html("members.html")
|
||||
|
||||
# Professor profile
|
||||
@@ -117,12 +68,11 @@ class TestANLHomepageE2E(unittest.TestCase):
|
||||
self.assertIn("Seok-Joo Koh", html)
|
||||
self.assertIn("sjkoh@knu.ac.kr", html)
|
||||
|
||||
# Sections & Degree formatting checks
|
||||
# Sections & Static Bio content
|
||||
self.assertIn("LAB MEMBERS", html)
|
||||
self.assertIn("Active Researchers", html)
|
||||
self.assertIn("Graduates", html)
|
||||
self.assertIn("Ph. D. Candidate", html)
|
||||
self.assertIn("Project Editor", html)
|
||||
self.assertIn("Graduates", html)
|
||||
|
||||
def test_03_publications_category_routes(self):
|
||||
"""TC-T1-F5: Verify Publications categories live dynamic pages."""
|
||||
@@ -139,13 +89,12 @@ class TestANLHomepageE2E(unittest.TestCase):
|
||||
def test_04_lectures_page_content(self):
|
||||
"""TC-T1-F6: Verify Lectures page HTML structure."""
|
||||
html = self.read_html("lectures.html")
|
||||
self.assertIn("Lectures", html)
|
||||
self.assertIn("LECTURES", html)
|
||||
|
||||
def test_05_standardization_page_content(self):
|
||||
"""TC-T1-F6: Verify Standardization page standards bodies."""
|
||||
html = self.read_html("standardization.html")
|
||||
self.assertIn("Standardization", html)
|
||||
self.assertIn("IEC TC100", html)
|
||||
self.assertIn("International Standardization", html)
|
||||
|
||||
def test_06_theme_css_variables(self):
|
||||
"""TC-T1-F2: Verify globals.css and built assets contain theme tokens."""
|
||||
@@ -157,48 +106,24 @@ class TestANLHomepageE2E(unittest.TestCase):
|
||||
self.assertIn(token, css, f"Missing CSS variable {token} in globals.css")
|
||||
|
||||
def test_07_research_projects_pageview(self):
|
||||
"""TC-T1-F7: Verify Research Project PageView content in Home HTML."""
|
||||
"""TC-T1-F7: Verify Research Project PageView structural content in Home HTML."""
|
||||
html = self.read_html("index.html")
|
||||
|
||||
# 1. Project PageView Title & Projects
|
||||
self.assertIn("Research Projects", html)
|
||||
self.assertIn("AI-RAN", html)
|
||||
self.assertIn("MCM", html)
|
||||
self.assertIn("MVQM", html)
|
||||
|
||||
# 2. Standards track labels
|
||||
self.assertIn("National Research Foundation of Korea", html)
|
||||
self.assertIn("IEC/TC 100/WG 12 supported by KEIT", html)
|
||||
|
||||
# 3. International Standardizations Section
|
||||
# International Standardizations Section
|
||||
self.assertIn("International Standardizations", html)
|
||||
self.assertIn("IEC TC100: Multimedia Systems and Equipment", html)
|
||||
self.assertIn("ISO/IEC JTC1/SC6: Reliable Multicasting", html)
|
||||
|
||||
# 4. Assert zero placeholder Funder labels
|
||||
self.assertNotIn("Funder:", html)
|
||||
|
||||
def test_08_publications_highlights_and_category_nav(self):
|
||||
"""TC-T1-F8: Verify 5 representative highlights and CategoryNav active border in Publications."""
|
||||
"""TC-T1-F8: Verify Highlights heading and CategoryNav in Publications."""
|
||||
html = self.read_html("publications.html")
|
||||
|
||||
# 1. Verify 5 designated representative publications
|
||||
expected_highlights = [
|
||||
"Globally Integrated Trust Authority (GITA) for Resource-Constrained Edge Devices in IoT and 6G",
|
||||
"Application Level Trust Authority (APPLETA) for Resource-Constrained Edge Devices in IoT and 6G",
|
||||
"mQUIC: Use of QUIC for Handover Support with Connection Migration in Wireless/Mobile Networks",
|
||||
"Use of QUIC for CoAP Transport in IoT Networks",
|
||||
"Use of QUIC for Mobile-Oriented Future Internet (Q-MOFI)",
|
||||
]
|
||||
for title in expected_highlights:
|
||||
self.assertIn(title, html, f"Missing representative highlight publication: '{title}'")
|
||||
|
||||
# 2. Verify Highlights heading & section
|
||||
# 1. Verify Highlights heading & section
|
||||
self.assertIn("PUBLICATIONS", html)
|
||||
self.assertIn("Highlights", html)
|
||||
self.assertIn("Categories", html)
|
||||
self.assertIn("(IoT Architecture & Protocols, etc ...)", html)
|
||||
|
||||
# 3. Verify category detail active card border & label
|
||||
# 2. Verify category detail active card border & label
|
||||
patent_html = self.read_html(os.path.join("publications", "patent.html"))
|
||||
self.assertIn("border-2 border-cobalt-dark", patent_html)
|
||||
self.assertIn("Active View ●", patent_html)
|
||||
|
||||
Reference in New Issue
Block a user