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

195 lines
6.1 KiB
Go

package languages
import (
"strings"
"testing"
"github.com/zzet/gortex/internal/graph"
)
// TestLiquidSectionSnippetNormalization proves render/include normalize to the
// theme-relative snippets/<name>.liquid file (and section to sections/<name>),
// whitespace-trim tags `{%- -%}` are accepted, and a {% schema %} block is
// recorded as a value-redacted constant (never the raw JSON, which carries
// store secrets). The normalized path is a real cross-file target a bare-name
// import never resolves.
func TestLiquidSectionSnippetNormalization(t *testing.T) {
src := "{%- render 'product-card' -%}\n" +
"{% include 'header' %}\n" +
"{% section 'announcement-bar' %}\n" +
"{% schema %}\n" +
"{ \"name\": \"My Section\", \"api_key\": \"sk-secret\" }\n" +
"{% endschema %}\n"
res, err := NewLiquidExtractor().Extract("templates/index.liquid", []byte(src))
if err != nil {
t.Fatal(err)
}
imports := map[string]bool{}
for _, e := range res.Edges {
if e.Kind == graph.EdgeImports {
imports[strings.TrimPrefix(e.To, "unresolved::import::")] = true
}
}
for _, want := range []string{
"snippets/product-card.liquid", // render, whitespace-trimmed
"snippets/header.liquid", // include
"sections/announcement-bar.liquid",
} {
if !imports[want] {
t.Errorf("missing normalized import %q (got %v)", want, keysOfBoolMap(imports))
}
}
var schema *graph.Node
for _, n := range res.Nodes {
if n.Kind == graph.KindConstant && n.Name == "schema" {
schema = n
}
}
if schema == nil {
t.Fatal("schema block produced no constant node")
}
if r, _ := schema.Meta["value_redacted"].(bool); !r {
t.Error("schema constant is not value_redacted")
}
// The raw JSON (and any secret in it) must not be in the graph.
for _, n := range res.Nodes {
for _, v := range n.Meta {
if s, ok := v.(string); ok && strings.Contains(s, "sk-secret") {
t.Errorf("a schema secret leaked into node %s meta", n.ID)
}
}
}
}
// TestMyBatisSqlFragmentIncludeRefs proves <sql> fragments become first-class
// nodes, <include refid> becomes a traversable reference (same-mapper and
// cross-mapper ns.X -> ns::X), and resultType/parameterType are captured — so
// fragment reuse is navigable, which a statement-only extractor drops.
func TestMyBatisSqlFragmentIncludeRefs(t *testing.T) {
src := `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.app.UserMapper">
<sql id="Base_Column_List">id, name, email</sql>
<select id="findUser" resultType="com.app.User" parameterType="long">
SELECT <include refid="Base_Column_List"/> FROM users WHERE id = #{id}
<include refid="com.app.CommonMapper.tenantFilter"/>
</select>
</mapper>
`
res, err := NewMyBatisExtractor().Extract("UserMapper.xml", []byte(src))
if err != nil {
t.Fatal(err)
}
// Fragment node exists, marked as a fragment.
var frag *graph.Node
var stmt *graph.Node
for _, n := range res.Nodes {
switch n.ID {
case "com.app.UserMapper::Base_Column_List":
frag = n
case "com.app.UserMapper::findUser":
stmt = n
}
}
if frag == nil {
t.Fatal("missing <sql> fragment node")
}
if f, _ := frag.Meta["mybatis_fragment"].(bool); !f {
t.Error("fragment node not marked mybatis_fragment")
}
if stmt == nil {
t.Fatal("missing statement node")
}
if stmt.Meta["mybatis_result_type"] != "com.app.User" {
t.Errorf("resultType=%v, want com.app.User", stmt.Meta["mybatis_result_type"])
}
if stmt.Meta["mybatis_parameter_type"] != "long" {
t.Errorf("parameterType=%v, want long", stmt.Meta["mybatis_parameter_type"])
}
// include refs: same-mapper + cross-mapper.
refs := map[string]bool{}
for _, e := range res.Edges {
if e.Kind == graph.EdgeReferences && e.Meta != nil && e.Meta["via"] == "mybatis.include" {
refs[e.From+" -> "+e.To] = true
}
}
if !refs["com.app.UserMapper::findUser -> com.app.UserMapper::Base_Column_List"] {
t.Error("missing same-mapper include reference to Base_Column_List")
}
if !refs["com.app.UserMapper::findUser -> com.app.CommonMapper::tenantFilter"] {
t.Error("missing cross-mapper include reference (ns.X -> ns::X) to CommonMapper.tenantFilter")
}
}
// TestDfmParentStackNesting proves true tree nesting — a nested control is a
// member of its IMMEDIATE parent, not the form root — and that a multi-line
// property value (`= ( … )`) carrying literal object/end tokens does not
// corrupt the depth tracking.
func TestDfmParentStackNesting(t *testing.T) {
src := "object Form1: TForm1\n" +
" object Panel1: TPanel\n" +
" object Button1: TButton\n" +
" OnClick = Button1Click\n" +
" end\n" +
" Items.Strings = (\n" +
" 'object FakeData'\n" +
" 'end'\n" +
" )\n" +
" end\n" +
"end\n"
res, err := NewDFMExtractor().Extract("Form1.dfm", []byte(src))
if err != nil {
t.Fatal(err)
}
members := map[string]string{}
for _, e := range res.Edges {
if e.Kind == graph.EdgeMemberOf {
members[e.From] = e.To
}
}
// Button1 is a member of Panel1 (its immediate parent), NOT Form1.
if members["Form1.dfm::Button1"] != "Form1.dfm::Panel1" {
t.Errorf("Button1 memberOf %q, want Form1.dfm::Panel1 (immediate parent)", members["Form1.dfm::Button1"])
}
if members["Form1.dfm::Panel1"] != "Form1.dfm::Form1" {
t.Errorf("Panel1 memberOf %q, want Form1.dfm::Form1", members["Form1.dfm::Panel1"])
}
// The multi-line property data ('object FakeData', 'end') must not have
// produced phantom nodes: exactly the 3 real controls.
objects := 0
for _, n := range res.Nodes {
if n.Kind == graph.KindType || n.Kind == graph.KindField {
objects++
}
}
if objects != 3 {
t.Errorf("extracted %d object nodes, want 3 (multi-line property data leaked)", objects)
}
if n := nodeByName(res.Nodes, "FakeData"); n != nil {
t.Error("a multi-line property data line was parsed as an object")
}
}
func keysOfBoolMap(m map[string]bool) []string {
out := make([]string, 0, len(m))
for k := range m {
out = append(out, k)
}
return out
}
func nodeByName(nodes []*graph.Node, name string) *graph.Node {
for _, n := range nodes {
if n.Name == name {
return n
}
}
return nil
}