Skip to content

Commit 194da05

Browse files
test(tmpl): env access, built-in defaults, extends+include integration
- env_test.go: 4 tests for {{ env.VAR }}, env conditionals, missing env (no error), built-in defaults (VLESS_ENC, LOG_LEVEL), override of built-in defaults - extends_test.go: integration test verifying {% extends %} + {% include %} in the same template — base inbound + child outbound + TLS partial all present in rendered output Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 32910b8 commit 194da05

2 files changed

Lines changed: 128 additions & 0 deletions

File tree

internal/tmpl/env_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package tmpl
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
)
7+
8+
func TestRender_EnvAccess(t *testing.T) {
9+
t.Setenv("HCH_TEST_VAR", "hello-from-env")
10+
src := []byte(`{"val": "{{ env.HCH_TEST_VAR }}"}`)
11+
out, _, err := Render(src, map[string]string{})
12+
if err != nil {
13+
t.Fatalf("Render: %v", err)
14+
}
15+
if !bytes.Contains(out, []byte("hello-from-env")) {
16+
t.Errorf("env var not substituted: %s", out)
17+
}
18+
}
19+
20+
func TestRender_EnvConditional(t *testing.T) {
21+
t.Setenv("HCH_TEST_FLAG", "1")
22+
src := []byte(`{"ok": {% if env.HCH_TEST_FLAG %}true{% else %}false{% endif %}}`)
23+
out, _, err := Render(src, map[string]string{})
24+
if err != nil {
25+
t.Fatalf("Render: %v", err)
26+
}
27+
if !bytes.Contains(out, []byte("true")) {
28+
t.Errorf("env conditional not working: %s", out)
29+
}
30+
}
31+
32+
func TestRender_EnvMissing(t *testing.T) {
33+
// Unset env var should render as empty string, not error.
34+
src := []byte(`{"val": "{{ env.HCH_DEFINITELY_NOT_SET_XYZ123 }}"}`)
35+
out, _, err := Render(src, map[string]string{})
36+
if err != nil {
37+
t.Fatalf("Render: %v", err)
38+
}
39+
if bytes.Contains(out, []byte("HCH_DEFINITELY")) {
40+
t.Errorf("missing env var leaked placeholder: %s", out)
41+
}
42+
}
43+
44+
func TestRender_BuiltinDefaults(t *testing.T) {
45+
// VLESS_ENC, VLESS_DEC, LOG_LEVEL should have defaults when not set.
46+
src := []byte(`{"enc":"{{ VLESS_ENC }}","dec":"{{ VLESS_DEC }}","log":"{{ LOG_LEVEL }}"}`)
47+
out, _, err := Render(src, map[string]string{})
48+
if err != nil {
49+
t.Fatalf("Render: %v", err)
50+
}
51+
if !bytes.Contains(out, []byte(`"none"`)) {
52+
t.Errorf("VLESS_ENC default not applied: %s", out)
53+
}
54+
if !bytes.Contains(out, []byte(`"error"`)) {
55+
t.Errorf("LOG_LEVEL default not applied: %s", out)
56+
}
57+
}
58+
59+
func TestRender_BuiltinOverridable(t *testing.T) {
60+
// Caller can override built-in defaults.
61+
src := []byte(`{"enc":"{{ VLESS_ENC }}"}`)
62+
out, _, err := Render(src, map[string]string{"VLESS_ENC": "xtls"})
63+
if err != nil {
64+
t.Fatalf("Render: %v", err)
65+
}
66+
if !bytes.Contains(out, []byte("xtls")) {
67+
t.Errorf("VLESS_ENC override not applied: %s", out)
68+
}
69+
}

internal/tmpl/extends_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package tmpl
2+
3+
import (
4+
"bytes"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
)
9+
10+
func TestRenderFile_ExtendsWithInclude(t *testing.T) {
11+
// Build a temp dir tree mirroring the xray example structure.
12+
root := t.TempDir()
13+
baseDir := filepath.Join(root, "templates", "base")
14+
tlsDir := filepath.Join(root, "templates", "tls")
15+
exDir := filepath.Join(root, "vless-xhttp")
16+
os.MkdirAll(baseDir, 0o755)
17+
os.MkdirAll(tlsDir, 0o755)
18+
os.MkdirAll(exDir, 0o755)
19+
20+
// Base template with block.
21+
os.WriteFile(filepath.Join(baseDir, "client.json.j2"), []byte(`{
22+
"inbounds": [{"tag":"socks-in","port":{{ SOCKS_PORT }}}],
23+
"outbounds": [{% block outbound %}{"tag":"direct"}{% endblock %}]
24+
}`), 0o644)
25+
26+
// TLS partial.
27+
os.WriteFile(filepath.Join(tlsDir, "client.tpl"), []byte(`"security": "{% if TLS %}tls{% else %}none{% endif %}",`), 0o644)
28+
29+
// Child template using extends + include.
30+
child := filepath.Join(exDir, "client.json.j2")
31+
os.WriteFile(child, []byte(`{% extends "../templates/base/client.json.j2" %}
32+
{% block outbound %}
33+
{
34+
"tag": "vless-out",
35+
"streamSettings": {
36+
{% include "../templates/tls/client.tpl" %}
37+
"network": "xhttp"
38+
}
39+
}
40+
{% endblock %}`), 0o644)
41+
42+
vars := map[string]string{"SOCKS_PORT": "1080", "TLS": "1"}
43+
out, _, err := RenderFile(child, vars)
44+
if err != nil {
45+
t.Fatalf("RenderFile: %v", err)
46+
}
47+
// Must have base inbound (socks-in) from extends.
48+
if !bytes.Contains(out, []byte("socks-in")) {
49+
t.Errorf("base inbound missing: %s", out)
50+
}
51+
// Must have child outbound (vless-out) from block.
52+
if !bytes.Contains(out, []byte("vless-out")) {
53+
t.Errorf("child outbound missing: %s", out)
54+
}
55+
// Must have TLS from include.
56+
if !bytes.Contains(out, []byte(`"tls"`)) {
57+
t.Errorf("tls include missing: %s", out)
58+
}
59+
}

0 commit comments

Comments
 (0)