~bigbes/sr-ht-dolt

ref: 0638c16f7ac54e5ab2e398ac748c28a1475ebb04 sr-ht-dolt/cmd/doltsrht-migrate/main_test.go -rw-r--r-- 3.6 KiB
0638c16f — Eugene Blikh migrate: install the instance's masking before brant can log a DSN 18 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
package main

import (
	"bytes"
	"errors"
	"os"
	"os/exec"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// testArgvEnv turns a re-executed copy of this test binary into the migrate
// binary itself: TestMain sees it, replaces os.Args with its whitespace-split
// value and calls main().
//
// Running main() in a subprocess is the only way to observe what main() does.
// It reads the config off the filesystem through config.LoadConfig, the record
// under test is written by a library past every seam a unit test could reach,
// and the run ends in os.Exit — which takes the process with it. What is under
// test is what an operator finds in the upgrade log, so the test has to read
// that log.
const testArgvEnv = "DOLTSRHT_MIGRATE_TEST_ARGV"

func TestMain(m *testing.M) {
	if argv, ok := os.LookupEnv(testArgvEnv); ok {
		os.Args = append([]string{"doltsrht-migrate"}, strings.Fields(argv)...)
		main()
		os.Exit(0)
	}
	os.Exit(m.Run())
}

// runMain runs main() in a subprocess whose working directory is empty — no
// config.ini, no ./migrations — and returns everything the process wrote and the
// status it exited with.
//
// Stdout and stderr are interleaved into one buffer because that is how a
// package manager's upgrade log shows them, and the question this asks is what
// an operator finds there.
func runMain(t *testing.T, argv string) (output string, code int) {
	t.Helper()

	exe, err := os.Executable()
	require.NoError(t, err, "locate the test binary to re-execute")

	cmd := exec.Command(exe)
	cmd.Dir = t.TempDir()
	cmd.Env = append(os.Environ(), testArgvEnv+"="+argv)
	var buf bytes.Buffer
	cmd.Stdout = &buf
	cmd.Stderr = &buf

	err = cmd.Run()
	var exit *exec.ExitError
	switch {
	case err == nil:
		code = 0
	case errors.As(err, &exit):
		code = exit.ExitCode()
	default:
		require.NoError(t, err, "run %s %s", exe, argv)
	}
	return buf.String(), code
}

// testPassword is a fake credential no configuration on this instance holds. It
// is written into the DSN the test passes and looked for in everything the
// process wrote; a real one would put the thing under test into the test log.
const testPassword = "HUNTER2SECRET"

// TestMainDoesNotPrintTheConnectionString pins the reason main() installs a
// logger at all.
//
// brant reports a migration directory it cannot open with
// `slog.Error("failed to create provider", "datasource", a.DataSourceName, ...)`
// — the connection string, password and all, at ERR level. A missing directory
// is not an exotic state: it is what an installed package is in until its first
// migration ships. Two things have to hold for the password not to reach the
// journal, and this asserts through both: the key "datasource" must be in the
// instance's mask list (sr-ht-ecore's, which did not know that spelling until it
// was added), and a handler carrying that list must be slog's default by the
// time brant writes, or the record goes to Go's built-in stderr handler and no
// list of any kind applies.
func TestMainDoesNotPrintTheConnectionString(t *testing.T) {
	out, code := runMain(t,
		"up --dir /nonexistent --dsn postgresql://u:"+testPassword+"@localhost/x")

	// The run must actually reach brant and fail there, or the assertions below
	// would pass over an empty log.
	assert.NotZero(t, code, "the missing directory must still be a failed run: %s", out)
	assert.Contains(t, out, "failed to create provider",
		"the leaking record must have been written at all: %s", out)

	assert.NotContains(t, out, testPassword, "the DSN reached the log in the clear: %s", out)
	assert.Contains(t, out, "datasource=***", "%s", out)
}