Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

166 lines
5.4 KiB
Go

package languages
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zzet/gortex/internal/graph"
)
func TestDockerfile_FromEmitsImageNodes(t *testing.T) {
src := []byte(`FROM golang:1.22 AS builder
FROM alpine:3.18
`)
e := NewDockerfileExtractor()
result, err := e.Extract("app.dockerfile", src)
require.NoError(t, err)
nodes := nodesByID(result.Nodes)
// Two stage Image nodes — `builder` (alias) and `stage-1`
// (synthetic name for the second, unaliased FROM).
require.Contains(t, nodes, "image::stage::app.dockerfile::builder")
require.Contains(t, nodes, "image::stage::app.dockerfile::stage-1")
// Two base Image nodes.
require.Contains(t, nodes, "image::golang:1.22")
require.Contains(t, nodes, "image::alpine:3.18")
// Stage → base via EdgeDependsOn.
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeDependsOn,
"image::stage::app.dockerfile::builder", "image::golang:1.22"))
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeDependsOn,
"image::stage::app.dockerfile::stage-1", "image::alpine:3.18"))
}
func TestDockerfile_MultiStageChain(t *testing.T) {
src := []byte(`FROM golang:1.22 AS builder
FROM builder AS shrunk
FROM alpine:3.18
COPY --from=shrunk /out /usr/bin/app
`)
e := NewDockerfileExtractor()
result, err := e.Extract("multi.dockerfile", src)
require.NoError(t, err)
// `shrunk` stage depends on the prior `builder` stage.
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeDependsOn,
"image::stage::multi.dockerfile::shrunk",
"image::stage::multi.dockerfile::builder"),
"shrunk should depend on builder via the stage chain")
}
func TestDockerfile_EnvEmitsConfigKeyAndUsesEnv(t *testing.T) {
src := []byte(`FROM ubuntu:22.04
ENV DATABASE_URL=postgres://localhost
ENV LOG_LEVEL=info
ARG BUILD_REV=dev
`)
e := NewDockerfileExtractor()
result, err := e.Extract("svc.dockerfile", src)
require.NoError(t, err)
nodes := nodesByID(result.Nodes)
stage := "image::stage::svc.dockerfile::stage-0"
for _, key := range []string{"DATABASE_URL", "LOG_LEVEL", "BUILD_REV"} {
id := "cfg::env::" + key
require.Contains(t, nodes, id)
assert.Equal(t, graph.KindConfigKey, nodes[id].Kind)
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeUsesEnv, stage, id),
"stage should declare uses_env for %q", key)
}
}
func TestDockerfile_ExposeEmitsExposes(t *testing.T) {
src := []byte(`FROM nginx:1.25
EXPOSE 80 443/tcp
EXPOSE 5353/udp
`)
e := NewDockerfileExtractor()
result, err := e.Extract("web.dockerfile", src)
require.NoError(t, err)
stage := "image::stage::web.dockerfile::stage-0"
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeExposes, stage, "port::tcp::80"))
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeExposes, stage, "port::tcp::443"))
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeExposes, stage, "port::udp::5353"))
}
func TestDockerfile_PreFromArgBindsToFile(t *testing.T) {
// Global build args declared before any FROM. The legacy
// KindVariable + KindConfigKey nodes still fire; the
// EdgeUsesEnv from the file (since no stage exists yet) is a
// regression guard for the pre-FROM cursor branch.
src := []byte(`ARG VERSION=1.0
FROM scratch
`)
e := NewDockerfileExtractor()
result, err := e.Extract("global.dockerfile", src)
require.NoError(t, err)
require.Contains(t, nodesByID(result.Nodes), "cfg::env::VERSION")
}
// TestDockerfile_ImageIDSharedAcrossExtractors validates the
// invariant that a Dockerfile FROM and a K8s container.image with
// the same image ref produce the same KindImage node — so a
// `find_usages` walk on the Image node returns both the Dockerfile
// stage and the K8s Resource.
func TestDockerfile_ImageIDSharedAcrossExtractors(t *testing.T) {
dockerfileSrc := []byte("FROM ghcr.io/acme/api:1.2.3 AS api\n")
dRes, err := NewDockerfileExtractor().Extract("svc/Dockerfile", dockerfileSrc)
require.NoError(t, err)
k8sSrc := []byte(`apiVersion: apps/v1
kind: Deployment
metadata:
name: api
namespace: prod
spec:
template:
spec:
containers:
- name: api
image: ghcr.io/acme/api:1.2.3
`)
kRes, err := NewYAMLExtractor().Extract("k8s/api.yaml", k8sSrc)
require.NoError(t, err)
dHas := false
for _, n := range dRes.Nodes {
if n.ID == "image::ghcr.io/acme/api:1.2.3" {
dHas = true
}
}
kHas := false
for _, n := range kRes.Nodes {
if n.ID == "image::ghcr.io/acme/api:1.2.3" {
kHas = true
}
}
assert.True(t, dHas, "Dockerfile should emit shared image node")
assert.True(t, kHas, "K8s should emit shared image node")
}
// TestDockerfile_ConfigKeyIDSharedWithGoOsGetenv validates the
// cross-ref invariant: a Dockerfile ENV NAME and a Go os.Getenv("NAME")
// emit the same `cfg::env::NAME` node ID. We only check the
// Dockerfile side here — the Go side is exercised by go_configs_test.go
// which already asserts the same ID convention. The pairing across
// the two emitters is the load-bearing property tested by this case.
func TestDockerfile_ConfigKeyIDSharedWithGoOsGetenv(t *testing.T) {
src := []byte("FROM scratch\nENV DATABASE_URL=postgres://...\n")
res, err := NewDockerfileExtractor().Extract("Dockerfile", src)
require.NoError(t, err)
found := false
for _, n := range res.Nodes {
if n.ID == "cfg::env::DATABASE_URL" && n.Kind == graph.KindConfigKey {
found = true
}
}
assert.True(t, found, "Dockerfile ENV must produce cfg::env::<NAME> matching os.Getenv convention")
}