1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: LicenseRef-MutuaL-1.2
4
5package configvalue
6
7import (
8 "context"
9 "os"
10 "runtime"
11 "strconv"
12 "strings"
13 "testing"
14 "time"
15)
16
17func TestResolveACIDConfigValuesResolution1InterpolatesEnvironment(t *testing.T) {
18 t.Setenv("COOKED_USERNAME_FROM_MY_SHELL", "ada")
19 t.Setenv("COOKED_SUFFIX", "lovelace")
20
21 tests := []struct {
22 name string
23 config string
24 want string
25 }{
26 {name: "plain literal", config: "literal", want: "literal"},
27 {name: "dollar prefix", config: "$COOKED_USERNAME_FROM_MY_SHELL", want: "ada"},
28 {name: "braced reference", config: "${COOKED_USERNAME_FROM_MY_SHELL}-${COOKED_SUFFIX}", want: "ada-lovelace"},
29 {name: "escaped dollar and bang", config: "$$$COOKED_USERNAME_FROM_MY_SHELL$!", want: "$ada!"},
30 }
31
32 resolver := NewResolver()
33 for _, tt := range tests {
34 t.Run(tt.name, func(t *testing.T) {
35 got, ok := resolver.Resolve(context.Background(), tt.config)
36 if !ok {
37 t.Fatalf("Resolve(%q) ok = false", tt.config)
38 }
39
40 if got != tt.want {
41 t.Fatalf("Resolve(%q) = %q, want %q", tt.config, got, tt.want)
42 }
43 })
44 }
45}
46
47func TestResolveACIDConfigValuesResolution1RejectsMissingEnvironment(t *testing.T) {
48 resolver := NewResolver()
49
50 got, ok := resolver.Resolve(context.Background(), "$COOKED_MISSING_VALUE")
51 if ok {
52 t.Fatalf("Resolve ok = true, got %q", got)
53 }
54}
55
56func TestResolveACIDConfigValuesCommands3CachesCommandResults(t *testing.T) {
57 if runtime.GOOS == "windows" {
58 t.Skip("shell command syntax is POSIX-specific")
59 }
60
61 resolver := NewResolver()
62 command := "!printf cooked"
63
64 first, ok := resolver.Resolve(context.Background(), command)
65 if !ok {
66 t.Fatal("first command resolution failed")
67 }
68
69 second, ok := resolver.Resolve(context.Background(), command)
70 if !ok {
71 t.Fatal("second command resolution failed")
72 }
73
74 if first != "cooked" || second != "cooked" {
75 t.Fatalf("resolved command values = %q and %q, want cooked", first, second)
76 }
77
78 resolver.CommandTimeout = time.Nanosecond
79 third, ok := resolver.Resolve(context.Background(), command)
80 if !ok || third != "cooked" {
81 t.Fatalf("cached command value = %q, %v; want cooked, true", third, ok)
82 }
83}
84
85func TestResolveACIDAuthenticationCredentials17RejectsOversizedCommandStdout(t *testing.T) {
86 t.Setenv("COOKED_CONFIGVALUE_FAKE_SHELL", "1")
87 t.Setenv("COOKED_CONFIGVALUE_FAKE_SHELL_STDOUT_BYTES", strconv.Itoa(maxCommandStdoutBytes+1))
88
89 resolver := NewResolver()
90 resolver.shellCommand = func(string) (string, []string) {
91 return os.Args[0], []string{"-test.run=TestConfigValueFakeShell"}
92 }
93
94 got, ok := resolver.Resolve(context.Background(), "!oversized")
95 if ok {
96 t.Fatalf("oversized command stdout resolved with length %d, want unresolved", len(got))
97 }
98 if got != "" {
99 t.Fatalf("oversized command stdout returned non-empty value length %d, want empty", len(got))
100 }
101}
102
103func TestConfigValueFakeShell(t *testing.T) {
104 if os.Getenv("COOKED_CONFIGVALUE_FAKE_SHELL") != "1" {
105 return
106 }
107
108 byteCount, err := strconv.Atoi(os.Getenv("COOKED_CONFIGVALUE_FAKE_SHELL_STDOUT_BYTES"))
109 if err != nil {
110 os.Exit(2)
111 }
112 if _, err := os.Stdout.WriteString(strings.Repeat("x", byteCount)); err != nil {
113 os.Exit(2)
114 }
115
116 os.Exit(0)
117}