diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index caa01593..186a7c11 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -235,6 +235,7 @@ jobs: for file in ${{ steps.changed-skills.outputs.all_changed_files }}; do SKILL_FILES="$SKILL_FILES ../$file" done + SKILLS="${SKILLS# }" # Run frontmatter spec validation if OUTPUT=$(npm run frontmatter -- $SKILL_FILES 2>&1); then diff --git a/plugin/skills/azure-kubernetes/SKILL.md b/plugin/skills/azure-kubernetes/SKILL.md new file mode 100644 index 00000000..f0343789 --- /dev/null +++ b/plugin/skills/azure-kubernetes/SKILL.md @@ -0,0 +1,127 @@ +--- +name: azure-kubernetes +metadata: + author: Microsoft + version: "1.0.0" +description: "Plan and create production-ready Azure Kubernetes Service (AKS) clusters. Covers Day-0 decisions and Day-1 configuration, cluster SKUs (Automatic vs Standard), security, monitoring, reliability/performance best practices, upgrades, and networking. WHEN: create AKS cluster, plan AKS configuration, design AKS networking, AKS Automatic vs Standard, AKS security, AKS upgrade strategy, AKS autoscaling, AKS monitoring setup, AKS cost analysis, Day-0 checklist." +--- + +# Azure Kubernetes Service + +> **AUTHORITATIVE GUIDANCE — MANDATORY COMPLIANCE** +> +> This skill produces a **recommended AKS cluster configuration** based on user requirements, distinguishing **Day-0 decisions** (networking, API server — hard to change later) from **Day-1 features** (can enable post-creation). See [CLI reference](./references/cli-reference.md) for commands. + +## Quick Reference +| Property | Value | +|----------|-------| +| Best for | AKS cluster planning and Day-0 decisions | +| MCP Tools | `mcp_azure_mcp_aks`, `mcp_aks_mcp_az_aks_operations` | +| CLI | `az aks create`, `az aks show` | +| Related skills | azure-diagnostics (troubleshooting), azure-deploy (app deployment) | + +## When to Use This Skill +Activate this skill when user wants to: +- Create a new AKS cluster +- Plan AKS cluster configuration for production workloads +- Design AKS networking (API server access, pod IP model, egress) +- Set up AKS identity and secrets management +- Configure AKS governance (Azure Policy, Deployment Safeguards) +- Enable AKS observability (monitoring, Prometheus, Grafana) +- Define AKS upgrade and patching strategy +- Enable AKS cost visibility and analysis +- Understand AKS Automatic vs Standard SKU differences +- Get a Day-0 checklist for AKS cluster setup and configuration + +## Rules +1. Start with the user's requirements for provisioning compute, networking, security, and other settings. +2. Use the AKS MCP server for invoking Azure API and kubectl commands when applicable during the cluster setup and operations processes. +3. Determine if AKS Automatic or Standard SKU is more appropriate based on the user's need for control vs convenience. Default to AKS Automatic unless specific customizations are required. +4. Document decisions and rationale for cluster configuration choices, especially for Day-0 decisions that are hard to change later (networking, API server access). + + +## Required Inputs (Ask only what’s needed) +If the user is unsure, use safe defaults. +- Cluster environment: dev/test or production +- Region(s), availability zones, preferred node VM sizes +- Expected scale (node/cluster count, workload size) +- Networking requirements (API server access, pod IP model, ingress/egress control) +- Security and identity requirements, including image registry +- Upgrade and observability preferences +- Cost constraints + +## Workflow + +### 1. Cluster Type +- **AKS Automatic** (default): Best for most production workloads, provides a curated experience with pre-configured best practices for security, reliability, and performance. Use unless you have specific custom requirements for networking, autoscaling, or node pool configurations not supported by NAP. +- **AKS Standard**: Use if you need full control over cluster configuration, will require additional overhead to setup and manage. + +### 2. Networking (Pod IP, Egress, Ingress, Dataplane) + +**Pod IP Model** (Key Day-0 decision): +- **Azure CNI Overlay** (recommended): pod IPs from private overlay range, not VNet-routable, scales to large clusters and good for most workloads +- **Azure CNI (VNet-routable)**: pod IPs directly from VNet (pod subnet or node subnet), use when pods must be directly addressable from VNet or on-prem + - Docs: https://learn.microsoft.com/azure/aks/azure-cni-overlay + +**Dataplane & Network Policy**: +- **Azure CNI powered by Cilium** (recommended): eBPF-based for high-performance packet processing, network policies, and observability + +**Egress**: +- **Static Egress Gateway** for stable, predictable outbound IPs +- For restricted egress: UDR + Azure Firewall or NVA + +**Ingress**: +- **App Routing addon with Gateway API** — recommended default for HTTP/HTTPS workloads +- **Istio service mesh with Gateway API** — for advanced traffic management, mTLS, canary deployments +- **Application Gateway for Containers** — for L7 load balancing with WAF integration + +**DNS**: +- Enable **LocalDNS** on all node pools for reliable, performant DNS resolution + +### 3. Security +- Use **Microsoft Entra ID** everywhere (control plane, Workload Identity for pods, node access). Avoid static credentials. +- Azure Key Vault via **Secrets Store CSI Driver** for secrets +- Enable **Azure Policy** + **Deployment Safeguards** +- Enable **Encryption at rest** for etcd/API server; **in-transit** for node-to-node +- Allow only signed, policy-approved images (Azure Policy + Ratify), prefer **Azure Container Registry** +- **Isolation**: Use namespaces, network policies, scoped logging + +### 4. Observability +- Use Azure Monitor and Container Insights for AKS monitoring enablement (logs + Prometheus + Grafana). + +### 5. Upgrades & Patching +- Configure **Maintenance Windows** for controlled upgrade timing +- Enable **auto-upgrades** for cluster and node OS to stay up-to-date with security patches and Kubernetes versions +- Consider **LTS versions** for enterprise stability (2-year support) by upgrading your cluster to the AKS Premium tier +- **Multi-cluster upgrades**: Use **AKS Fleet Manager** for staged rollout across test → production clusters + +### 6. Performance +- Use **Ephemeral OS disks** (`--node-osdisk-type Ephemeral`) for faster node startup +- Select **Azure Linux** as node OS (smaller footprint, faster boot) +- Enable **KEDA** for event-driven autoscaling beyond HPA + +### 7. Node Pools & Compute +- **Dedicated system node pool**: At least 2 nodes, tainted for system workloads only (`CriticalAddonsOnly`) +- Enable **Node Auto Provisioning (NAP)** on all pools for cost savings and responsive scaling +- Use **latest generation SKUs (v5/v6)** for host-level optimizations +- **Avoid B-series VMs** — burstable SKUs cause performance/reliability issues +- Use SKUs with **at least 4 vCPUs** for production workloads +- Set **topology spread constraints** to distribute pods across hosts/zones per SLO + +### 8. Reliability +- Deploy across **3 Availability Zones** (`--zones 1 2 3`) +- Use **Standard tier** for zone-redundant control plane + 99.95% SLA for API server availability +- Enable **Microsoft Defender for Containers** for runtime protection +- Configure **PodDisruptionBudgets** for all production workloads +- Use **topology spread constraints** to ensure pod distribution across failure domains + +### 9. Cost Controls +- Use **Spot node pools** for batch/interruptible workloads (up to 90% savings) +- **Stop/Start** dev/test clusters: `az aks stop/start` +- Consider **Reserved Instances** or **Savings Plans** for steady-state workloads + +## Guardrails / Safety +- Do not request or output secrets (tokens, keys, subscription IDs). +- If requirements are ambiguous for day-0 critical decisions, ask the user clarifying questions. For day-1 enabled features, propose 2–3 safe options with tradeoffs and choose a conservative default. +- Do not promise zero downtime; advise workload safeguards (PDBs, probes, replicas) and staged upgrades along with best practices for reliability and performance. +- If user asks for actions that require privileged access, provide a plan and commands with placeholders. \ No newline at end of file diff --git a/plugin/skills/azure-kubernetes/references/cli-reference.md b/plugin/skills/azure-kubernetes/references/cli-reference.md new file mode 100644 index 00000000..5969dcd6 --- /dev/null +++ b/plugin/skills/azure-kubernetes/references/cli-reference.md @@ -0,0 +1,33 @@ +# CLI Reference for AKS + +```bash +# List AKS clusters +az aks list --output table + +# Show cluster details +az aks show --name --resource-group + +# Get available Kubernetes versions +az aks get-versions --location --output table + +# Create AKS Automatic cluster +az aks create --name --resource-group --sku automatic \ + --network-plugin azure --network-plugin-mode overlay \ + --enable-oidc-issuer --enable-workload-identity + +# Create AKS Standard cluster +az aks create --name --resource-group \ + --node-count 3 --zones 1 2 3 \ + --network-plugin azure --network-plugin-mode overlay \ + --enable-cluster-autoscaler --min-count 1 --max-count 10 + +# Get credentials +az aks get-credentials --name --resource-group + +# List node pools +az aks nodepool list --cluster-name --resource-group --output table + +# Enable monitoring +az aks enable-addons --name --resource-group \ + --addons monitoring --workspace-resource-id +``` \ No newline at end of file diff --git a/plugin/skills/azure-prepare/references/architecture.md b/plugin/skills/azure-prepare/references/architecture.md index e340c7a1..c009d3a9 100644 --- a/plugin/skills/azure-prepare/references/architecture.md +++ b/plugin/skills/azure-prepare/references/architecture.md @@ -21,6 +21,32 @@ Select hosting stack and map components to Azure services. | Long-running processes | ✓✓ | | ✓ | | Minimal ops overhead | | ✓✓ | ✓ | +### Container Hosting: Container Apps vs AKS + +| Factor | Container Apps | AKS | +|--------|:--------------:|:---:| +| **Scale to zero** | ✓✓ | | +| **Kubernetes API access** | | ✓✓ | +| **Custom operators/CRDs** | | ✓✓ | +| **Service mesh** | Dapr (built-in) | Istio, Cilium | +| **GPU workloads** | | ✓✓ | +| **Best for** | Microservices, event-driven | Full K8s control, complex workloads | + +#### When to Use Container Apps +- Microservices without Kubernetes complexity +- Event-driven workloads (KEDA built-in) +- Need scale-to-zero for cost optimization +- Teams without Kubernetes expertise + +#### When to Use AKS +- Need Kubernetes API/kubectl access +- Require custom operators or CRDs +- Service mesh requirements (Istio, Linkerd) +- GPU/ML workloads +- Complex networking or multi-tenant architectures + +> **AKS Planning:** For AKS SKU selection (Automatic vs Standard), networking, identity, scaling, and security configuration, invoke the **azure-kubernetes** skill. + ## Service Mapping ### Hosting @@ -28,11 +54,13 @@ Select hosting stack and map components to Azure services. | Component Type | Primary Service | Alternatives | |----------------|-----------------|--------------| | SPA Frontend | Static Web Apps | Blob + CDN | -| SSR Web App | Container Apps | App Service | -| REST/GraphQL API | Container Apps | App Service, Functions | -| Background Worker | Container Apps | Functions | -| Scheduled Task | Functions (Timer) | Container Apps Jobs | -| Event Processor | Functions | Container Apps | +| SSR Web App | Container Apps | App Service, AKS | +| REST/GraphQL API | Container Apps | App Service, Functions, AKS | +| Background Worker | Container Apps | Functions, AKS | +| Scheduled Task | Functions (Timer) | Container Apps Jobs, Kubernetes CronJob (on AKS) | +| Event Processor | Functions | Container Apps, AKS + KEDA | +| Microservices (full K8s) | AKS | Container Apps | +| GPU/ML Workloads | AKS | Azure ML | ### Data diff --git a/tests/azure-kubernetes/__snapshots__/triggers.test.ts.snap b/tests/azure-kubernetes/__snapshots__/triggers.test.ts.snap new file mode 100644 index 00000000..87a08b43 --- /dev/null +++ b/tests/azure-kubernetes/__snapshots__/triggers.test.ts.snap @@ -0,0 +1,103 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`azure-kubernetes - Trigger Tests Trigger Keywords Snapshot skill description triggers match snapshot 1`] = ` +{ + "description": "Plan and create production-ready Azure Kubernetes Service (AKS) clusters. Covers Day-0 decisions and Day-1 configuration, cluster SKUs (Automatic vs Standard), security, monitoring, reliability/performance best practices, upgrades, and networking. WHEN: create AKS cluster, plan AKS configuration, design AKS networking, AKS Automatic vs Standard, AKS security, AKS upgrade strategy, AKS autoscaling, AKS monitoring setup, AKS cost analysis, Day-0 checklist.", + "extractedKeywords": [ + "aks", + "analysis", + "automatic", + "autoscaling", + "azure", + "best", + "checklist", + "cli", + "cluster", + "clusters", + "configuration", + "container", + "cost", + "covers", + "create", + "day-0", + "day-1", + "decisions", + "deploy", + "design", + "diagnostic", + "entra", + "identity", + "key vault", + "kubernetes", + "mcp", + "monitor", + "monitoring", + "networking", + "observability", + "performance", + "plan", + "practices", + "production-ready", + "reliability", + "security", + "service", + "setup", + "skus", + "standard", + "strategy", + "upgrade", + "upgrades", + "when", + ], + "name": "azure-kubernetes", +} +`; + +exports[`azure-kubernetes - Trigger Tests Trigger Keywords Snapshot skill keywords match snapshot 1`] = ` +[ + "aks", + "analysis", + "automatic", + "autoscaling", + "azure", + "best", + "checklist", + "cli", + "cluster", + "clusters", + "configuration", + "container", + "cost", + "covers", + "create", + "day-0", + "day-1", + "decisions", + "deploy", + "design", + "diagnostic", + "entra", + "identity", + "key vault", + "kubernetes", + "mcp", + "monitor", + "monitoring", + "networking", + "observability", + "performance", + "plan", + "practices", + "production-ready", + "reliability", + "security", + "service", + "setup", + "skus", + "standard", + "strategy", + "upgrade", + "upgrades", + "when", +] +`; diff --git a/tests/azure-kubernetes/integration.test.ts b/tests/azure-kubernetes/integration.test.ts new file mode 100644 index 00000000..2e66b24f --- /dev/null +++ b/tests/azure-kubernetes/integration.test.ts @@ -0,0 +1,271 @@ +/** + * Integration Tests for azure-kubernetes + * + * Tests skill behavior with a real Copilot agent session. + * Runs prompts multiple times to measure skill invocation rate. + * + * Prerequisites: + * 1. npm install -g @github/copilot-cli + * 2. Run `copilot` and authenticate + */ + +import { + useAgentRunner, + shouldSkipIntegrationTests, + getIntegrationSkipReason +} from "../utils/agent-runner"; +import { softCheckSkill } from "../utils/evaluate"; + +const SKILL_NAME = "azure-kubernetes"; +const RUNS_PER_PROMPT = 5; + +// Check if integration tests should be skipped at module level +const skipTests = shouldSkipIntegrationTests(); +const skipReason = getIntegrationSkipReason(); + +// Log skip reason if skipping +if (skipTests && skipReason) { + console.log(`⏭️ Skipping integration tests: ${skipReason}`); +} + +const describeIntegration = skipTests ? describe.skip : describe; + +describeIntegration(`${SKILL_NAME}_ - Integration Tests`, () => { + const agent = useAgentRunner(); + + describe("skill-invocation", () => { + test("invokes azure-kubernetes skill for AKS cluster creation prompt", async () => { + for (let i = 0; i < RUNS_PER_PROMPT; i++) { + try { + const agentMetadata = await agent.run({ + prompt: "Help me create a production-ready AKS cluster with best practices" + }); + + softCheckSkill(agentMetadata, SKILL_NAME); + } catch (e: unknown) { + if (e instanceof Error && e.message?.includes("Failed to load @github/copilot-sdk")) { + console.log("⏭️ SDK not loadable, skipping test"); + return; + } + throw e; + } + } + }); + + test("invokes azure-kubernetes skill for AKS networking prompt", async () => { + for (let i = 0; i < RUNS_PER_PROMPT; i++) { + try { + const agentMetadata = await agent.run({ + prompt: "What networking options should I choose for my AKS cluster?" + }); + + softCheckSkill(agentMetadata, SKILL_NAME); + } catch (e: unknown) { + if (e instanceof Error && e.message?.includes("Failed to load @github/copilot-sdk")) { + console.log("⏭️ SDK not loadable, skipping test"); + return; + } + throw e; + } + } + }); + + test("invokes azure-kubernetes skill for AKS Automatic vs Standard", async () => { + for (let i = 0; i < RUNS_PER_PROMPT; i++) { + try { + const agentMetadata = await agent.run({ + prompt: "Should I use AKS Automatic or AKS Standard for my production workloads?" + }); + + softCheckSkill(agentMetadata, SKILL_NAME); + } catch (e: unknown) { + if (e instanceof Error && e.message?.includes("Failed to load @github/copilot-sdk")) { + console.log("⏭️ SDK not loadable, skipping test"); + return; + } + throw e; + } + } + }); + + test("invokes azure-kubernetes skill for golden path AKS setup", async () => { + for (let i = 0; i < RUNS_PER_PROMPT; i++) { + try { + const agentMetadata = await agent.run({ + prompt: "What is the recommended golden path for setting up an AKS cluster?" + }); + + softCheckSkill(agentMetadata, SKILL_NAME); + } catch (e: unknown) { + if (e instanceof Error && e.message?.includes("Failed to load @github/copilot-sdk")) { + console.log("⏭️ SDK not loadable, skipping test"); + return; + } + throw e; + } + } + }); + + test("invokes azure-kubernetes skill for AKS security best practices", async () => { + for (let i = 0; i < RUNS_PER_PROMPT; i++) { + try { + const agentMetadata = await agent.run({ + prompt: "What are the security best practices for AKS clusters?" + }); + + softCheckSkill(agentMetadata, SKILL_NAME); + } catch (e: unknown) { + if (e instanceof Error && e.message?.includes("Failed to load @github/copilot-sdk")) { + console.log("⏭️ SDK not loadable, skipping test"); + return; + } + throw e; + } + } + }); + + test("invokes azure-kubernetes skill for AKS performance optimization", async () => { + for (let i = 0; i < RUNS_PER_PROMPT; i++) { + try { + const agentMetadata = await agent.run({ + prompt: "How do I optimize performance for my Azure Kubernetes workloads?" + }); + + softCheckSkill(agentMetadata, SKILL_NAME); + } catch (e: unknown) { + if (e instanceof Error && e.message?.includes("Failed to load @github/copilot-sdk")) { + console.log("⏭️ SDK not loadable, skipping test"); + return; + } + throw e; + } + } + }); + + test("invokes azure-kubernetes skill for AKS reliability patterns", async () => { + for (let i = 0; i < RUNS_PER_PROMPT; i++) { + try { + const agentMetadata = await agent.run({ + prompt: "What reliability best practices should I follow for AKS?" + }); + + softCheckSkill(agentMetadata, SKILL_NAME); + } catch (e: unknown) { + if (e instanceof Error && e.message?.includes("Failed to load @github/copilot-sdk")) { + console.log("⏭️ SDK not loadable, skipping test"); + return; + } + throw e; + } + } + }); + + test("invokes azure-kubernetes skill for workload identity setup", async () => { + for (let i = 0; i < RUNS_PER_PROMPT; i++) { + try { + const agentMetadata = await agent.run({ + prompt: "How do I configure workload identity for my AKS cluster?" + }); + + softCheckSkill(agentMetadata, SKILL_NAME); + } catch (e: unknown) { + if (e instanceof Error && e.message?.includes("Failed to load @github/copilot-sdk")) { + console.log("⏭️ SDK not loadable, skipping test"); + return; + } + throw e; + } + } + }); + + test("invokes azure-kubernetes skill for AKS monitoring setup", async () => { + for (let i = 0; i < RUNS_PER_PROMPT; i++) { + try { + const agentMetadata = await agent.run({ + prompt: "Set up monitoring for my Azure Kubernetes cluster with Prometheus and Grafana" + }); + + softCheckSkill(agentMetadata, SKILL_NAME); + } catch (e: unknown) { + if (e instanceof Error && e.message?.includes("Failed to load @github/copilot-sdk")) { + console.log("⏭️ SDK not loadable, skipping test"); + return; + } + throw e; + } + } + }); + + test("invokes azure-kubernetes skill for AKS upgrade strategy", async () => { + for (let i = 0; i < RUNS_PER_PROMPT; i++) { + try { + const agentMetadata = await agent.run({ + prompt: "What is the best upgrade strategy for AKS clusters in production?" + }); + + softCheckSkill(agentMetadata, SKILL_NAME); + } catch (e: unknown) { + if (e instanceof Error && e.message?.includes("Failed to load @github/copilot-sdk")) { + console.log("⏭️ SDK not loadable, skipping test"); + return; + } + throw e; + } + } + }); + + test("invokes azure-kubernetes skill for AKS autoscaling configuration", async () => { + for (let i = 0; i < RUNS_PER_PROMPT; i++) { + try { + const agentMetadata = await agent.run({ + prompt: "How do I configure cluster autoscaler and KEDA for my AKS cluster?" + }); + + softCheckSkill(agentMetadata, SKILL_NAME); + } catch (e: unknown) { + if (e instanceof Error && e.message?.includes("Failed to load @github/copilot-sdk")) { + console.log("⏭️ SDK not loadable, skipping test"); + return; + } + throw e; + } + } + }); + + test("invokes azure-kubernetes skill for AKS deployment safeguards", async () => { + for (let i = 0; i < RUNS_PER_PROMPT; i++) { + try { + const agentMetadata = await agent.run({ + prompt: "Configure deployment safeguards and Azure Policy for my AKS cluster" + }); + + softCheckSkill(agentMetadata, SKILL_NAME); + } catch (e: unknown) { + if (e instanceof Error && e.message?.includes("Failed to load @github/copilot-sdk")) { + console.log("⏭️ SDK not loadable, skipping test"); + return; + } + throw e; + } + } + }); + + test("invokes azure-kubernetes skill for AKS node pool sizing", async () => { + for (let i = 0; i < RUNS_PER_PROMPT; i++) { + try { + const agentMetadata = await agent.run({ + prompt: "How should I size my AKS node pools for a microservices application?" + }); + + softCheckSkill(agentMetadata, SKILL_NAME); + } catch (e: unknown) { + if (e instanceof Error && e.message?.includes("Failed to load @github/copilot-sdk")) { + console.log("⏭️ SDK not loadable, skipping test"); + return; + } + throw e; + } + } + }); + }); +}); \ No newline at end of file diff --git a/tests/azure-kubernetes/triggers.test.ts b/tests/azure-kubernetes/triggers.test.ts new file mode 100644 index 00000000..d7e3ed39 --- /dev/null +++ b/tests/azure-kubernetes/triggers.test.ts @@ -0,0 +1,95 @@ +/** + * Trigger Tests for azure-kubernetes + * + * Tests that verify the skill triggers on appropriate prompts + * and does NOT trigger on unrelated prompts. + * + * Uses snapshot testing + parameterized tests for comprehensive coverage. + */ + +import { TriggerMatcher } from "../utils/trigger-matcher"; +import { loadSkill, LoadedSkill } from "../utils/skill-loader"; + +const SKILL_NAME = "azure-kubernetes"; + +describe(`${SKILL_NAME} - Trigger Tests`, () => { + let triggerMatcher: TriggerMatcher; + let skill: LoadedSkill; + + beforeAll(async () => { + skill = await loadSkill(SKILL_NAME); + triggerMatcher = new TriggerMatcher(skill); + }); + + describe("Should Trigger", () => { + // Prompts that SHOULD trigger this skill - include multiple keywords + const shouldTriggerPrompts: string[] = [ + "Create an Azure Kubernetes cluster for production", + "Set up AKS cluster with Azure networking", + "Configure Azure AKS cluster autoscaling", + "Plan Azure Kubernetes workload identity setup", + "Azure AKS Automatic vs Standard cluster", + "Set up Azure Kubernetes monitoring with Prometheus", + "Configure Azure AKS deployment safeguards", + "Azure Kubernetes cluster upgrade strategy", + ]; + + test.each(shouldTriggerPrompts)('triggers on: "%s"', (prompt) => { + const result = triggerMatcher.shouldTrigger(prompt); + expect(result.triggered).toBe(true); + }); + }); + + describe("Should NOT Trigger", () => { + // Prompts that should NOT trigger this skill (avoid Azure/kubernetes/AKS keywords) + const shouldNotTriggerPrompts: string[] = [ + "What is the weather today?", + "Help me write a poem", + "Explain quantum computing", + "Help me with AWS EKS", + "How do I use Google GKE?", + "Write a Python script to parse JSON", + "What is the capital of France?", + "Configure my local Docker container", + "Set up PostgreSQL database locally", + ]; + + test.each(shouldNotTriggerPrompts)('does not trigger on: "%s"', (prompt) => { + const result = triggerMatcher.shouldTrigger(prompt); + expect(result.triggered).toBe(false); + }); + }); + + describe("Trigger Keywords Snapshot", () => { + test("skill keywords match snapshot", () => { + expect(triggerMatcher.getKeywords()).toMatchSnapshot(); + }); + + test("skill description triggers match snapshot", () => { + expect({ + name: skill.metadata.name, + description: skill.metadata.description, + extractedKeywords: triggerMatcher.getKeywords(), + }).toMatchSnapshot(); + }); + }); + + describe("Edge Cases", () => { + test("handles empty prompt", () => { + const result = triggerMatcher.shouldTrigger(""); + expect(result.triggered).toBe(false); + }); + + test("handles very long prompt", () => { + const longPrompt = "AKS cluster Azure ".repeat(1000); + const result = triggerMatcher.shouldTrigger(longPrompt); + expect(typeof result.triggered).toBe("boolean"); + }); + + test("is case insensitive", () => { + const result1 = triggerMatcher.shouldTrigger("azure kubernetes cluster"); + const result2 = triggerMatcher.shouldTrigger("AZURE KUBERNETES CLUSTER"); + expect(result1.triggered).toBe(result2.triggered); + }); + }); +}); diff --git a/tests/azure-kubernetes/unit.test.ts b/tests/azure-kubernetes/unit.test.ts new file mode 100644 index 00000000..a8593aa1 --- /dev/null +++ b/tests/azure-kubernetes/unit.test.ts @@ -0,0 +1,111 @@ +/** + * Unit Tests for azure-kubernetes + * + * Tests domain invariants - concepts that should always be present + * in AKS cluster planning guidance. + */ + +import { loadSkill, LoadedSkill } from "../utils/skill-loader"; + +const SKILL_NAME = "azure-kubernetes"; + +describe(`${SKILL_NAME} - Unit Tests`, () => { + let skill: LoadedSkill; + + beforeAll(async () => { + skill = await loadSkill(SKILL_NAME); + }); + + describe("Skill Metadata", () => { + test("has valid SKILL.md with required fields", () => { + expect(skill.metadata).toBeDefined(); + expect(skill.metadata.name).toBe(SKILL_NAME); + expect(skill.metadata.description).toBeDefined(); + expect(skill.metadata.description.length).toBeGreaterThan(10); + }); + + test("description mentions AKS or Kubernetes", () => { + const description = skill.metadata.description.toLowerCase(); + expect(description).toMatch(/aks|kubernetes/); + }); + }); + + describe("Day-0 vs Day-1 Guidance", () => { + test("distinguishes Day-0 decisions from Day-1 features", () => { + expect(skill.content).toContain("Day-0"); + expect(skill.content).toContain("Day-1"); + }); + + test("identifies networking as hard-to-change decision", () => { + const content = skill.content.toLowerCase(); + // Networking is a Day-0 decision that's hard to change after cluster creation + expect(content).toMatch(/network|cni|pod ip/i); + }); + }); + + describe("Cluster SKU Guidance", () => { + test("covers AKS Automatic vs Standard choice", () => { + expect(skill.content).toContain("Automatic"); + expect(skill.content).toContain("Standard"); + }); + + test("recommends AKS Automatic as default for most workloads", () => { + const content = skill.content.toLowerCase(); + // AKS Automatic should be the recommended default + expect(content).toMatch(/automatic.*default|default.*automatic/); + }); + }); + + describe("Networking Guidance", () => { + test("covers pod IP model options", () => { + const content = skill.content.toLowerCase(); + expect(content).toMatch(/overlay|vnet|cni/); + }); + + test("mentions egress configuration", () => { + const content = skill.content.toLowerCase(); + expect(content).toMatch(/egress|outbound/); + }); + + test("mentions ingress options", () => { + const content = skill.content.toLowerCase(); + expect(content).toMatch(/ingress|gateway/); + }); + }); + + describe("Security Guidance", () => { + test("recommends Entra ID / managed identity", () => { + const content = skill.content.toLowerCase(); + expect(content).toMatch(/entra|workload identity|managed identity/); + }); + + test("mentions secrets management", () => { + const content = skill.content.toLowerCase(); + expect(content).toMatch(/key vault|secret/); + }); + + test("mentions policy or governance", () => { + const content = skill.content.toLowerCase(); + expect(content).toMatch(/policy|safeguard|governance/); + }); + }); + + describe("Observability Guidance", () => { + test("mentions monitoring or observability", () => { + const content = skill.content.toLowerCase(); + expect(content).toMatch(/monitor|observ|prometheus|grafana|insights/); + }); + }); + + describe("Reliability & Upgrades", () => { + test("mentions availability zones", () => { + const content = skill.content.toLowerCase(); + expect(content).toMatch(/zone|az\b/); + }); + + test("covers upgrade strategy", () => { + const content = skill.content.toLowerCase(); + expect(content).toMatch(/upgrade|patch|maintenance/); + }); + }); +}); \ No newline at end of file diff --git a/tests/skills.json b/tests/skills.json index dc5f8cd9..c64da42e 100644 --- a/tests/skills.json +++ b/tests/skills.json @@ -10,6 +10,7 @@ "azure-deploy", "azure-diagnostics", "azure-hosted-copilot-sdk", + "azure-kubernetes", "azure-kusto", "azure-messaging", "azure-observability", @@ -25,6 +26,6 @@ "integrationTestSchedule": { "0 5 * * 2-6": "microsoft-foundry", "0 8 * * 2-6": "azure-deploy", - "0 12 * * 2-6": "appinsights-instrumentation,azure-ai,azure-aigateway,azure-cloud-migrate,azure-compliance,azure-compute,azure-cost-optimization,azure-diagnostics,azure-hosted-copilot-sdk,azure-kusto,azure-messaging,azure-observability,azure-prepare,azure-rbac,azure-resource-lookup,azure-resource-visualizer,azure-storage,azure-validate,entra-app-registration" + "0 12 * * 2-6": "appinsights-instrumentation,azure-ai,azure-aigateway,azure-cloud-migrate,azure-compliance,azure-compute,azure-cost-optimization,azure-diagnostics,azure-hosted-copilot-sdk,azure-kubernetes,azure-kusto,azure-messaging,azure-observability,azure-prepare,azure-rbac,azure-resource-lookup,azure-resource-visualizer,azure-storage,azure-validate,entra-app-registration" } } \ No newline at end of file