mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 09:13:58 +00:00
Phase 0: test pipeline foundation — pyproject.toml, IntelliJ run configs, coverage fix, welcome mode selector
Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
parent
22906ee656
commit
03d89eaa53
24
.idea/runConfigurations/pytest_all.xml
Normal file
24
.idea/runConfigurations/pytest_all.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="pytest — all (with coverage)" type="PythonConfigurationType" factoryName="Python">
|
||||
<module name="knoe-db" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONPATH" value="$PROJECT_DIR$" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="IS_MODULE_SDK" value="false" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
||||
<option name="SCRIPT_NAME" value="-m pytest" />
|
||||
<option name="PARAMETERS" value="--cov=knoe --cov-report=html --cov-report=term-missing tests/" />
|
||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||
<option name="EMULATE_TERMINAL" value="false" />
|
||||
<option name="MODULE_MODE" value="true" />
|
||||
<option name="REDIRECT_INPUT" value="false" />
|
||||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
24
.idea/runConfigurations/pytest_gke.xml
Normal file
24
.idea/runConfigurations/pytest_gke.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="pytest — gke mode" type="PythonConfigurationType" factoryName="Python">
|
||||
<module name="knoe-db" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONPATH" value="$PROJECT_DIR$" />
|
||||
<env name="KNOE_MODE" value="gke" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="IS_MODULE_SDK" value="false" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<option name="SCRIPT_NAME" value="-m pytest" />
|
||||
<option name="PARAMETERS" value="-m gke --cov=knoe --cov-report=term-missing -v tests/" />
|
||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||
<option name="EMULATE_TERMINAL" value="false" />
|
||||
<option name="MODULE_MODE" value="true" />
|
||||
<option name="REDIRECT_INPUT" value="false" />
|
||||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
24
.idea/runConfigurations/pytest_k3d.xml
Normal file
24
.idea/runConfigurations/pytest_k3d.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="pytest — k3d mode" type="PythonConfigurationType" factoryName="Python">
|
||||
<module name="knoe-db" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONPATH" value="$PROJECT_DIR$" />
|
||||
<env name="KNOE_MODE" value="k3d" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="IS_MODULE_SDK" value="false" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<option name="SCRIPT_NAME" value="-m pytest" />
|
||||
<option name="PARAMETERS" value="-m k3d --cov=knoe --cov-report=term-missing -v tests/" />
|
||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||
<option name="EMULATE_TERMINAL" value="false" />
|
||||
<option name="MODULE_MODE" value="true" />
|
||||
<option name="REDIRECT_INPUT" value="false" />
|
||||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
24
.idea/runConfigurations/pytest_min.xml
Normal file
24
.idea/runConfigurations/pytest_min.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="pytest — min mode" type="PythonConfigurationType" factoryName="Python">
|
||||
<module name="knoe-db" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONPATH" value="$PROJECT_DIR$" />
|
||||
<env name="KNOE_MODE" value="min" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="IS_MODULE_SDK" value="false" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<option name="SCRIPT_NAME" value="-m pytest" />
|
||||
<option name="PARAMETERS" value="-m min --cov=knoe --cov-report=term-missing -v tests/" />
|
||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||
<option name="EMULATE_TERMINAL" value="false" />
|
||||
<option name="MODULE_MODE" value="true" />
|
||||
<option name="REDIRECT_INPUT" value="false" />
|
||||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
23
.idea/runConfigurations/pytest_unit.xml
Normal file
23
.idea/runConfigurations/pytest_unit.xml
Normal file
@ -0,0 +1,23 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="pytest — unit (fast, no deps)" type="PythonConfigurationType" factoryName="Python">
|
||||
<module name="knoe-db" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONPATH" value="$PROJECT_DIR$" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="IS_MODULE_SDK" value="false" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<option name="SCRIPT_NAME" value="-m pytest" />
|
||||
<option name="PARAMETERS" value="-m "unit and not integration" --cov=knoe --cov-report=term-missing -v tests/" />
|
||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||
<option name="EMULATE_TERMINAL" value="false" />
|
||||
<option name="MODULE_MODE" value="true" />
|
||||
<option name="REDIRECT_INPUT" value="false" />
|
||||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@ -390,6 +390,8 @@ class KnoeInstaller(
|
||||
)
|
||||
self.project_root = PROJECT_ROOT
|
||||
self.cfg_path = resolved_cfg
|
||||
from knoe.core.env import DEFAULT_ACTION_FLAGS
|
||||
self._action_flags: dict[str, bool] = DEFAULT_ACTION_FLAGS.copy()
|
||||
self._init_shared_state()
|
||||
self._init_database_options_state()
|
||||
self.screens = None
|
||||
@ -525,7 +527,7 @@ class KnoeInstaller(
|
||||
]
|
||||
self.nav_widgets = {}
|
||||
self._mode_tab_widgets: dict = {}
|
||||
self.deployment_mode = tk.StringVar(value=__import__("os").environ.get("KNOE_MODE", "k3s"))
|
||||
self.deployment_mode = tk.StringVar(value=__import__("os").environ.get("KNOE_MODE", ""))
|
||||
self._create_sidebar_nav()
|
||||
|
||||
# Validation attributes
|
||||
|
||||
@ -262,44 +262,23 @@ class NavigationMixin:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
_MODE_COLORS = {"k3d": "#4A90D9", "k3s": "#27AE60", "k8s": "#E67E22"}
|
||||
_MODE_COLORS = {"min": "#8E44AD", "k3d": "#4A90D9", "k3s": "#27AE60", "gke": "#E67E22"}
|
||||
|
||||
def _set_deployment_mode(self, mode: str) -> None:
|
||||
"""Switch the active deployment mode and persist it to the environment."""
|
||||
self.deployment_mode.set(mode)
|
||||
os.environ["KNOE_MODE"] = mode
|
||||
self._refresh_mode_tabs()
|
||||
|
||||
def _refresh_mode_tabs(self) -> None:
|
||||
"""Update tab highlight colours to reflect the active deployment mode."""
|
||||
active = self.deployment_mode.get()
|
||||
for value, widget in self._mode_tab_widgets.items():
|
||||
if value == active:
|
||||
widget.configure(bg=self._MODE_COLORS.get(value, "#888"), fg="white")
|
||||
else:
|
||||
widget.configure(bg="#D5D5C5", fg="#555")
|
||||
"""No-op: mode tabs replaced by welcome screen card selector."""
|
||||
pass
|
||||
|
||||
def _is_min_mode(self) -> bool:
|
||||
"""Return True when the user has selected minimal (containerd-only) mode."""
|
||||
return self.deployment_mode.get() == "min"
|
||||
|
||||
def _create_sidebar_nav(self):
|
||||
"""Create the left-hand navigation menu."""
|
||||
# ── Deployment mode tab strip ────────────────────────────────────
|
||||
mode_frame = tk.Frame(self.sidebar, bg="#F5F5DC")
|
||||
mode_frame.pack(fill="x", padx=12, pady=(14, 4))
|
||||
for label, value in [("k3d", "k3d"), ("k3s", "k3s"), ("k8s", "k8s")]:
|
||||
btn = tk.Label(
|
||||
mode_frame,
|
||||
text=label,
|
||||
bg="#D5D5C5",
|
||||
fg="#555",
|
||||
font=("SF Pro Text", 9, "bold"),
|
||||
padx=8,
|
||||
pady=3,
|
||||
cursor="hand2",
|
||||
)
|
||||
btn.pack(side="left", padx=2)
|
||||
btn.bind("<Button-1>", lambda e, v=value: self._set_deployment_mode(v))
|
||||
self._mode_tab_widgets[value] = btn
|
||||
self._refresh_mode_tabs()
|
||||
|
||||
# ── INSTALLER label ──────────────────────────────────────────────
|
||||
tk.Label(
|
||||
self.sidebar,
|
||||
@ -375,7 +354,10 @@ class NavigationMixin:
|
||||
self.show_page(seq[-1] if seq else "deps_summary")
|
||||
return
|
||||
if current_id == "env_setup":
|
||||
self.show_page("network_scan")
|
||||
if self._is_min_mode():
|
||||
self.show_page("deps_summary")
|
||||
else:
|
||||
self.show_page("network_scan")
|
||||
return
|
||||
if current_id == "init_cluster":
|
||||
if getattr(self, "_showing_service_overlay", False):
|
||||
@ -397,7 +379,10 @@ class NavigationMixin:
|
||||
self.show_page("common_services")
|
||||
return
|
||||
if current_id == "init_password":
|
||||
self.show_page("init_db_build")
|
||||
if self._is_min_mode():
|
||||
self.show_page("env_setup")
|
||||
else:
|
||||
self.show_page("init_db_build")
|
||||
return
|
||||
if current_id == "init_db_build":
|
||||
self.show_page("database_options")
|
||||
@ -426,6 +411,10 @@ class NavigationMixin:
|
||||
if current_id == "init_cnpg_deploy":
|
||||
self.show_page("supabase_config")
|
||||
return
|
||||
if current_id == "security":
|
||||
if self._is_min_mode():
|
||||
self.show_page("init_scripts")
|
||||
return
|
||||
if current_id == "create_installer":
|
||||
self.show_page("init_cnpg_deploy")
|
||||
return
|
||||
@ -492,6 +481,9 @@ class NavigationMixin:
|
||||
return
|
||||
|
||||
if current_id == "network_scan":
|
||||
if self._is_min_mode():
|
||||
self.show_page("env_setup")
|
||||
return
|
||||
# Capture network scan info
|
||||
try:
|
||||
self.knoe_cfg_data["Network"][
|
||||
@ -593,7 +585,10 @@ class NavigationMixin:
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
self.show_page("init_cluster")
|
||||
if self._is_min_mode():
|
||||
self.show_page("init_password")
|
||||
else:
|
||||
self.show_page("init_cluster")
|
||||
return
|
||||
|
||||
if current_id == "init_password":
|
||||
@ -678,7 +673,9 @@ class NavigationMixin:
|
||||
"Completed" if getattr(self, "_scripts_success", False) else "Attempted"
|
||||
)
|
||||
self._save_knoe_cfg()
|
||||
if self.kerberos_enabled.get():
|
||||
if self._is_min_mode():
|
||||
self.show_page("security")
|
||||
elif self.kerberos_enabled.get():
|
||||
self.show_page("kerberos_config")
|
||||
else:
|
||||
self.show_page("argocd_config")
|
||||
@ -885,7 +882,12 @@ class NavigationMixin:
|
||||
|
||||
# Page-specific adjustments
|
||||
pid = self.pages[self.page_index][0]
|
||||
if pid == "build":
|
||||
if pid == "welcome":
|
||||
if not self.deployment_mode.get():
|
||||
self.next_button.configure(state="disabled")
|
||||
else:
|
||||
self.next_button.configure(state="normal")
|
||||
elif pid == "build":
|
||||
# Build page: show Build or Next depending on state
|
||||
if getattr(self, "_built_success", False):
|
||||
self.next_button.configure(text="Next")
|
||||
|
||||
@ -1,68 +1,198 @@
|
||||
"""Welcome / splash screen."""
|
||||
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox, filedialog
|
||||
from knoe import screen as ui
|
||||
|
||||
_WELCOME_CARDS = [
|
||||
{
|
||||
"mode": "min",
|
||||
"title": "Just a Database",
|
||||
"body": "One knoe-db container via containerd.\nNo Kubernetes needed. Perfect for\ndeveloping a Spring app locally.",
|
||||
"badge": "Homebrew + 1Password",
|
||||
"color": "#8E44AD",
|
||||
},
|
||||
{
|
||||
"mode": "k3d",
|
||||
"title": "Local Cluster",
|
||||
"body": "k3s-in-Docker cluster on your Mac.\nFull CNPG database — add Supabase,\nArgoCD, Gitea or GitLab.",
|
||||
"badge": "Docker + Homebrew + 1Password",
|
||||
"color": "#4A90D9",
|
||||
},
|
||||
{
|
||||
"mode": "k3s",
|
||||
"title": "Homelab",
|
||||
"body": "Multi-node k3s on real hardware.\nFull Kerberos auth stack, Garage S3\nand monitoring.",
|
||||
"badge": "k3sup + Homebrew + 1Password",
|
||||
"color": "#27AE60",
|
||||
},
|
||||
{
|
||||
"mode": "gke",
|
||||
"title": "Production",
|
||||
"body": "Dual GKE clusters on Google Cloud.\nCNPG + GCS backups and Workload\nIdentity.",
|
||||
"badge": "gcloud + 1Password",
|
||||
"color": "#E67E22",
|
||||
},
|
||||
]
|
||||
|
||||
_MODE_TO_CLUSTER_ENV = {"min": "min", "k3d": "dev", "k3s": "service", "gke": "prod"}
|
||||
|
||||
_MODE_NEEDS = {
|
||||
"min": "You'll need: Homebrew + 1Password",
|
||||
"k3d": "You'll need: Docker, Homebrew + 1Password",
|
||||
"k3s": "You'll need: k3sup, Homebrew + 1Password",
|
||||
"gke": "You'll need: gcloud CLI + 1Password",
|
||||
}
|
||||
|
||||
|
||||
class WelcomeScreenMixin:
|
||||
"""Welcome / splash screen."""
|
||||
|
||||
def _render_welcome_page(self):
|
||||
# Letterhead at top right
|
||||
content_width = self.bg_canvas.winfo_width() or 975 # 1300 * 0.75 approx
|
||||
content_width = self.bg_canvas.winfo_width() or 975
|
||||
right_margin = content_width - 48
|
||||
|
||||
ui.canvas_text(
|
||||
self,
|
||||
right_margin,
|
||||
40,
|
||||
"knoe.dev",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 32, "bold"),
|
||||
anchor="ne",
|
||||
)
|
||||
ui.canvas_text(
|
||||
self,
|
||||
right_margin,
|
||||
85,
|
||||
"infrastructure.auto()",
|
||||
fill="#6e6e73",
|
||||
font=("SF Pro Text", 18),
|
||||
anchor="ne",
|
||||
)
|
||||
# Letterhead
|
||||
ui.canvas_text(self, right_margin, 40, "knoe.dev",
|
||||
fill="#6e6e73", font=("SF Pro Text", 32, "bold"), anchor="ne")
|
||||
ui.canvas_text(self, right_margin, 85, "infrastructure.auto()",
|
||||
fill="#6e6e73", font=("SF Pro Text", 18), anchor="ne")
|
||||
|
||||
# Welcome title
|
||||
self._render_title("Welcome", y=150)
|
||||
self._render_title("Welcome to Knoe.DB", y=145)
|
||||
|
||||
welcome_text = (
|
||||
"This installer will guide you through the process of setting up the Knoe Database and its supporting "
|
||||
"infrastructure. We have designed this process to be as automated as possible, ensuring that your "
|
||||
"deployment is secure, efficient, and tailored to your specific network environment.\n\n"
|
||||
"What to expect:\n"
|
||||
"• Network Environment Discovery: We'll scan for existing services like Active Directory and DNS.\n"
|
||||
"• System Configuration: Setting up local paths and environment variables.\n"
|
||||
"• Dependency Management: Ensuring all required tools (Docker, k3d, etc.) are ready.\n"
|
||||
"• Database Initialization: Configuring passwords, Kerberos authentication, and deploying the database cluster.\n\n"
|
||||
"We are excited to have you join our community and start building with us. "
|
||||
"Welcome to the neighborhood! Let's get started by preparing your system for the Knoe experience."
|
||||
intro = (
|
||||
"Knoe.DB Installer sets up a production-grade PostgreSQL cluster with Kerberos "
|
||||
"authentication.\nPick the mode that matches your hardware — we'll walk you through every step."
|
||||
)
|
||||
ui.canvas_text(self, 48, 200, intro,
|
||||
fill="#1d1d1f", font=("SF Pro Text", 13), width=800)
|
||||
|
||||
ui.canvas_text(
|
||||
self,
|
||||
48,
|
||||
220,
|
||||
welcome_text,
|
||||
fill="black",
|
||||
font=("SF Pro Text", 13),
|
||||
width=750,
|
||||
# Mode selector cards
|
||||
card_w = 185
|
||||
card_h = 200
|
||||
card_gap = 18
|
||||
card_x0 = 48
|
||||
card_y0 = 265
|
||||
|
||||
self._welcome_card_rects = {}
|
||||
|
||||
for i, card in enumerate(_WELCOME_CARDS):
|
||||
x0 = card_x0 + i * (card_w + card_gap)
|
||||
x1 = x0 + card_w
|
||||
y0 = card_y0
|
||||
y1 = card_y0 + card_h
|
||||
|
||||
bg = self.bg_canvas.create_rectangle(
|
||||
x0, y0, x1, y1, fill="white", outline="#CCCCCC", width=2,
|
||||
)
|
||||
self._canvas_items.append(bg)
|
||||
|
||||
bar = self.bg_canvas.create_rectangle(
|
||||
x0 + 1, y0 + 1, x1 - 1, y0 + 6, fill=card["color"], outline="",
|
||||
)
|
||||
self._canvas_items.append(bar)
|
||||
|
||||
mode_lbl = self.bg_canvas.create_text(
|
||||
x0 + 14, y0 + 18, text=card["mode"].upper(),
|
||||
fill=card["color"], font=("SF Pro Text", 9, "bold"), anchor="nw",
|
||||
)
|
||||
self._canvas_items.append(mode_lbl)
|
||||
|
||||
title_lbl = self.bg_canvas.create_text(
|
||||
x0 + 14, y0 + 36, text=card["title"],
|
||||
fill="#1d1d1f", font=("SF Pro Text", 13, "bold"), anchor="nw",
|
||||
)
|
||||
self._canvas_items.append(title_lbl)
|
||||
|
||||
body_lbl = self.bg_canvas.create_text(
|
||||
x0 + 14, y0 + 62, text=card["body"],
|
||||
fill="#555555", font=("SF Pro Text", 11), anchor="nw",
|
||||
width=card_w - 28,
|
||||
)
|
||||
self._canvas_items.append(body_lbl)
|
||||
|
||||
badge_lbl = self.bg_canvas.create_text(
|
||||
x0 + 14, y1 - 26, text=card["badge"],
|
||||
fill=card["color"], font=("SF Pro Text", 9), anchor="nw",
|
||||
width=card_w - 28,
|
||||
)
|
||||
self._canvas_items.append(badge_lbl)
|
||||
|
||||
self._welcome_card_rects[card["mode"]] = bg
|
||||
|
||||
mode = card["mode"]
|
||||
for item_id in (bg, bar, mode_lbl, title_lbl, body_lbl, badge_lbl):
|
||||
self.bg_canvas.tag_bind(
|
||||
item_id, "<Button-1>",
|
||||
lambda e, m=mode: self._on_welcome_card_click(m),
|
||||
)
|
||||
self.bg_canvas.tag_bind(
|
||||
item_id, "<Enter>",
|
||||
lambda e, r=bg, m=mode: self.bg_canvas.itemconfig(
|
||||
r, outline=self._MODE_COLORS.get(m, "#4A90D9")
|
||||
if self.deployment_mode.get() != m else None
|
||||
),
|
||||
)
|
||||
self.bg_canvas.tag_bind(
|
||||
item_id, "<Leave>",
|
||||
lambda e, r=bg, m=mode: self.bg_canvas.itemconfig(
|
||||
r, outline=self._MODE_COLORS.get(m, "#4A90D9")
|
||||
if self.deployment_mode.get() == m else "#CCCCCC"
|
||||
),
|
||||
)
|
||||
|
||||
# "You'll need" status line
|
||||
status_y = card_y0 + card_h + 20
|
||||
self._splash_status_item = self.bg_canvas.create_text(
|
||||
48, status_y,
|
||||
text="← Select a mode above to continue",
|
||||
fill="#8B8B7A", font=("SF Pro Text", 12, "italic"), anchor="nw",
|
||||
)
|
||||
self._canvas_items.append(self._splash_status_item)
|
||||
|
||||
# Restore highlight if a mode was already chosen (e.g. loaded from config)
|
||||
current = self.deployment_mode.get()
|
||||
if current and current in self._welcome_card_rects:
|
||||
self._highlight_welcome_card(current)
|
||||
try:
|
||||
self.bg_canvas.itemconfig(
|
||||
self._splash_status_item,
|
||||
text=_MODE_NEEDS.get(current, ""),
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Ensure footer is updated (Next button visible)
|
||||
self.update_footer()
|
||||
|
||||
def _on_welcome_card_click(self, mode: str) -> None:
|
||||
self.deployment_mode.set(mode)
|
||||
os.environ["KNOE_MODE"] = mode
|
||||
try:
|
||||
self.cluster_env.set(_MODE_TO_CLUSTER_ENV.get(mode, "dev"))
|
||||
except Exception:
|
||||
pass
|
||||
self._highlight_welcome_card(mode)
|
||||
try:
|
||||
if self._splash_status_item is not None:
|
||||
self.bg_canvas.itemconfig(
|
||||
self._splash_status_item,
|
||||
text=_MODE_NEEDS.get(mode, ""),
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
self.update_footer()
|
||||
|
||||
def _highlight_welcome_card(self, selected_mode: str) -> None:
|
||||
for m, rect_id in getattr(self, "_welcome_card_rects", {}).items():
|
||||
if m == selected_mode:
|
||||
color = self._MODE_COLORS.get(m, "#4A90D9")
|
||||
self.bg_canvas.itemconfig(rect_id, outline=color, width=3)
|
||||
else:
|
||||
self.bg_canvas.itemconfig(rect_id, outline="#CCCCCC", width=2)
|
||||
|
||||
def _start_welcome_dependency_scan(self):
|
||||
self.splash_scan_running = True
|
||||
self.splash_scan_done_at = None
|
||||
|
||||
36
pyproject.toml
Normal file
36
pyproject.toml
Normal file
@ -0,0 +1,36 @@
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
python_files = ["test_*.py"]
|
||||
python_classes = ["Test*"]
|
||||
python_functions = ["test_*"]
|
||||
addopts = "-ra --tb=short"
|
||||
|
||||
markers = [
|
||||
"unit: pure unit test — no external services, no cluster, runs anywhere",
|
||||
"integration: requires a running service, cluster, or network dependency",
|
||||
"min: tests the min (containerd) deployment path",
|
||||
"k3d: tests the k3d (local Docker cluster) deployment path",
|
||||
"k3s: tests the k3s (homelab) deployment path",
|
||||
"gke: tests the GKE (production) deployment path",
|
||||
]
|
||||
|
||||
[tool.coverage.run]
|
||||
source = ["knoe"]
|
||||
branch = true
|
||||
omit = [
|
||||
"tests/*",
|
||||
"*/__init__.py",
|
||||
"knoe/ui/*", # UI layer tested via integration only
|
||||
]
|
||||
|
||||
[tool.coverage.report]
|
||||
show_missing = true
|
||||
skip_covered = false
|
||||
# Raised incrementally as each pipeline phase lands — see docs/pipeline-phases.md Appendix C
|
||||
fail_under = 0
|
||||
|
||||
[tool.coverage.xml]
|
||||
output = "coverage.xml"
|
||||
|
||||
[tool.coverage.html]
|
||||
directory = "htmlcov"
|
||||
8
requirements-test.txt
Normal file
8
requirements-test.txt
Normal file
@ -0,0 +1,8 @@
|
||||
# Test-only dependencies — install alongside requirements.txt in CI and local dev
|
||||
# pip install -r requirements.txt -r requirements-test.txt
|
||||
|
||||
pytest>=7.4
|
||||
pytest-cov>=4.1
|
||||
pytest-mock>=3.11
|
||||
coverage[toml]>=7.3
|
||||
vulture>=2.10
|
||||
@ -1,17 +1,25 @@
|
||||
#!/bin/bash
|
||||
# Run all unit tests and generate a coverage report
|
||||
# Run all unit tests and generate a coverage report.
|
||||
# Coverage config is in pyproject.toml [tool.coverage.*].
|
||||
|
||||
export PYTHONPATH=$PYTHONPATH:.
|
||||
set -euo pipefail
|
||||
|
||||
# Check if pytest-cov is installed
|
||||
if pytest --trace-config | grep -q "pytest_cov"; then
|
||||
export PYTHONPATH="${PYTHONPATH:+$PYTHONPATH:}."
|
||||
|
||||
if python -c "import pytest_cov" 2>/dev/null; then
|
||||
echo "Running tests with coverage..."
|
||||
pytest --cov=knoe --cov=install --cov-report=term-missing --cov-report=html tests/
|
||||
pytest \
|
||||
--cov=knoe \
|
||||
--cov-report=term-missing \
|
||||
--cov-report=html \
|
||||
--cov-report=xml \
|
||||
tests/
|
||||
RET=$?
|
||||
echo "Coverage report (HTML) generated in htmlcov/index.html"
|
||||
echo "HTML coverage report: htmlcov/index.html"
|
||||
echo "XML coverage report: coverage.xml"
|
||||
exit $RET
|
||||
else
|
||||
echo "Warning: pytest-cov not found. Running tests without coverage."
|
||||
echo "To enable coverage, install it via: pip install pytest-cov"
|
||||
echo "Install it with: pip install -r requirements-test.txt"
|
||||
pytest tests/
|
||||
fi
|
||||
|
||||
Loading…
Reference in New Issue
Block a user