// Copyright (C) 2026 Dcntrlized
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package registry
import (
"context"
"encoding/json"
"os"
"strings"
"testing"
"time"
"git.dcntrlized.org/Dcntrlized/Maestro/lib/types"
)
// TestSourceHrefAllowlist pins audit finding N7: the registry item's Source
// comes verbatim from the image's OCI annotations, so the href the console
// renders must be gated by the same http/https allowlist as the Web App chip.
// An invalid source must never yield a usable href — it is served only as
// plain display text.
func TestSourceHrefAllowlist(t *testing.T) {
valid := []string{
"https://github.com/Dcntrlized/Maestro",
"https://git.dcntrlized.org/Dcntrlized/Calendar.git",
"http://192.168.122.183:3000/Dcntrlized/KeepNote",
"https://example.com/x?a=b#frag",
"http://[::1]:8080/repo",
}
for _, raw := range valid {
if got := sourceHrefFor(raw); got != raw {
t.Errorf("valid source %q: got href %q, want it verbatim", raw, got)
}
}
invalid := []string{
"javascript:alert(1)",
"JAVASCRIPT:alert(1)",
"data:text/html,",
"vbscript:msgbox(1)",
"file:///etc/passwd",
"https://user:pass@example.com/repo",
"https://user@example.com/repo",
"github.com/Dcntrlized/Maestro", // relative: no scheme
"//example.com/repo", // scheme-relative
"/repo", // path only
"", // absent
" ", // whitespace only
"https://example.com/re po", // space
"https://example.com/", // angle brackets
"https://example.com/repo\njavascript:1", // newline
"https://example.com/repo\x00tail", // NUL
"https://example.com/repo\x7f", // DEL
"https:", // no host
"ftp://example.com/repo", // wrong scheme
}
for _, raw := range invalid {
if got := sourceHrefFor(raw); got != "" {
t.Errorf("invalid source %q must not become an href, got %q", raw, got)
}
}
}
// TestSourceHrefWireKey locks the console contract: the validated href rides
// as `source_href` next to the raw `source`, so the frontend can render plain
// text for an invalid value instead of an anchor.
func TestSourceHrefWireKey(t *testing.T) {
b, err := json.Marshal(types.RegistryContainer{
Name: "keepnote",
Source: "javascript:alert(1)",
SourceHref: "",
})
if err != nil {
t.Fatalf("marshal: %v", err)
}
if !strings.Contains(string(b), `"source":"javascript:alert(1)"`) {
t.Fatalf("raw source must stay on the wire for display: %s", b)
}
if strings.Contains(string(b), "source_href") {
t.Fatalf("an invalid source must not emit source_href: %s", b)
}
b, err = json.Marshal(types.RegistryContainer{
Name: "keepnote",
Source: "https://github.com/Dcntrlized/KeepNote",
SourceHref: "https://github.com/Dcntrlized/KeepNote",
})
if err != nil {
t.Fatalf("marshal: %v", err)
}
if !strings.Contains(string(b), `"source_href":"https://github.com/Dcntrlized/KeepNote"`) {
t.Fatalf("valid source must emit source_href: %s", b)
}
}
// TestItemByNameLive resolves the infrastructure services against a live
// registry. It is opt-in (MAESTRO_REGISTRY_TEST=1) so the default test run
// stays offline; the dev VM and prod CI both set the Gitea env the function
// reads. Infrastructure services must resolve so Maestro can auto-install them
// on a fresh host (ensureInfraRunning).
func TestItemByNameLive(t *testing.T) {
if os.Getenv("MAESTRO_REGISTRY_TEST") != "1" {
t.Skip("set MAESTRO_REGISTRY_TEST=1 with GITEA_* env to run against a live registry")
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
for _, name := range []string{"dcntrlized-db", "dcntrlized-auth"} {
item, err := ItemByName(ctx, name)
if err != nil {
t.Fatalf("ItemByName(%q): %v", name, err)
}
if item.Image == "" {
t.Errorf("ItemByName(%q): empty image", name)
}
if !strings.HasPrefix(item.URL, "http") {
t.Errorf("ItemByName(%q): url %q not an http(s) compose source", name, item.URL)
}
t.Logf("resolved %s -> image=%s url=%s version=%s", name, item.Image, item.URL, item.Version)
}
}
// TestServiceNameFromLabels locks the "is this package a Maestro service?" rule:
// the org.dcntrlized.service label must be declared by the image (manifest
// annotations or config labels). The runner job image dcntrlized/ci-tools
// declares neither and must not surface in the console.
func TestServiceNameFromLabels(t *testing.T) {
if got := serviceNameFromLabels(map[string]string{"org.dcntrlized.service": "KeepNote"}, nil); got != "KeepNote" {
t.Fatalf("annotation: got %q", got)
}
if got := serviceNameFromLabels(nil, map[string]string{"org.dcntrlized.service": "Calendar"}); got != "Calendar" {
t.Fatalf("label: got %q", got)
}
// Annotations win when both are present.
if got := serviceNameFromLabels(
map[string]string{"org.dcntrlized.service": "KeepNote"},
map[string]string{"org.dcntrlized.service": "Calendar"},
); got != "KeepNote" {
t.Fatalf("annotation precedence: got %q", got)
}
if got := serviceNameFromLabels(nil, nil); got != "" {
t.Fatalf("no label must yield empty, got %q", got)
}
if got := serviceNameFromLabels(map[string]string{"org.opencontainers.image.title": "ci-tools"}, nil); got != "" {
t.Fatalf("unrelated labels must yield empty, got %q", got)
}
}
// TestBaseURLNeverFallsBackToPublicInDev locks the dev registry seam: with
// DCNTRLIZED_DEV=true Maestro must talk to the debug registry
// (DCNTRLIZED_GITEA_URL) only — an unset dev URL yields "" (loud failure),
// never the public git.dcntrlized.org.
func TestBaseURLNeverFallsBackToPublicInDev(t *testing.T) {
t.Setenv("DCNTRLIZED_DEV", "true")
t.Setenv("DCNTRLIZED_GITEA_URL", "http://192.168.122.183:3000")
if got := BaseURL(); got != "http://192.168.122.183:3000" {
t.Fatalf("dev with DCNTRLIZED_GITEA_URL: got %q", got)
}
t.Setenv("DCNTRLIZED_GITEA_URL", "")
if got := BaseURL(); got != "" {
t.Fatalf("dev without DCNTRLIZED_GITEA_URL must not fall back to public, got %q", got)
}
t.Setenv("DCNTRLIZED_DEV", "")
t.Setenv("DCNTRLIZED_GITEA_URL", "http://192.168.122.183:3000")
if got := BaseURL(); got != "https://git.dcntrlized.org" {
t.Fatalf("production must use the public registry, got %q", got)
}
}