|
| 1 | +package main |
| 2 | + |
| 3 | +import ( |
| 4 | + "bytes" |
| 5 | + "database/sql" |
| 6 | + "fmt" |
| 7 | + "os" |
| 8 | + "path/filepath" |
| 9 | + "testing" |
| 10 | + |
| 11 | + "github.com/stretchr/testify/require" |
| 12 | + "github.com/stretchr/testify/suite" |
| 13 | + "github.com/stripe/pg-schema-diff/internal/pgengine" |
| 14 | +) |
| 15 | + |
| 16 | +type cmdTestSuite struct { |
| 17 | + suite.Suite |
| 18 | + pgEngine *pgengine.Engine |
| 19 | +} |
| 20 | + |
| 21 | +func (suite *cmdTestSuite) SetupSuite() { |
| 22 | + pgEngine, err := pgengine.StartEngine() |
| 23 | + suite.Require().NoError(err) |
| 24 | + suite.pgEngine = pgEngine |
| 25 | +} |
| 26 | + |
| 27 | +func (suite *cmdTestSuite) TearDownSuite() { |
| 28 | + suite.Require().NoError(suite.pgEngine.Close()) |
| 29 | +} |
| 30 | + |
| 31 | +type runCmdWithAssertionsParams struct { |
| 32 | + args []string |
| 33 | + // dynamicArgs is function that can be used to build args that are dynamic, i.e., |
| 34 | + // saving schemas to a randomly generated temporary directory. |
| 35 | + dynamicArgs []dArgGenerator |
| 36 | + |
| 37 | + // outputContains is a list of substrings that are expected to be contained in the stdout output of the command. |
| 38 | + outputContains []string |
| 39 | + // expectErrContains is a list of substrings that are expected to be contained in the error returned by |
| 40 | + // cmd.RunE. This is DISTINCT from stdErr. |
| 41 | + expectErrContains []string |
| 42 | +} |
| 43 | + |
| 44 | +func (suite *cmdTestSuite) runCmdWithAssertions(tc runCmdWithAssertionsParams) { |
| 45 | + args := tc.args |
| 46 | + for _, da := range tc.dynamicArgs { |
| 47 | + args = append(args, da(suite.T())...) |
| 48 | + } |
| 49 | + |
| 50 | + rootCmd := buildRootCmd() |
| 51 | + rootCmd.SetArgs(args) |
| 52 | + stdOut := &bytes.Buffer{} |
| 53 | + rootCmd.SetOut(stdOut) |
| 54 | + stdErr := &bytes.Buffer{} |
| 55 | + rootCmd.SetErr(stdErr) |
| 56 | + |
| 57 | + err := rootCmd.Execute() |
| 58 | + if len(tc.expectErrContains) > 0 { |
| 59 | + for _, e := range tc.expectErrContains { |
| 60 | + suite.ErrorContains(err, e) |
| 61 | + } |
| 62 | + } else { |
| 63 | + stdErrStr := stdErr.String() |
| 64 | + suite.Require().NoError(err) |
| 65 | + // Only assert the std error is empty if we don't expect an error |
| 66 | + suite.Empty(stdErrStr, "expected no stderr") |
| 67 | + } |
| 68 | + |
| 69 | + stdOutStr := stdOut.String() |
| 70 | + if len(tc.outputContains) > 0 { |
| 71 | + for _, o := range tc.outputContains { |
| 72 | + suite.Contains(stdOutStr, o) |
| 73 | + } |
| 74 | + } |
| 75 | +} |
| 76 | + |
| 77 | +// dArgGenerator generates argument at the run-time of the test case... |
| 78 | +// intended for resources that are not known at test start and potentially need |
| 79 | +// to be cleaned up. |
| 80 | +type dArgGenerator func(*testing.T) []string |
| 81 | + |
| 82 | +func tempSchemaDirDArg(argName string, ddl []string) dArgGenerator { |
| 83 | + return func(t *testing.T) []string { |
| 84 | + t.Helper() |
| 85 | + return []string{"--" + argName, tempSchemaDir(t, ddl)} |
| 86 | + } |
| 87 | +} |
| 88 | + |
| 89 | +func tempSchemaDir(t *testing.T, ddl []string) string { |
| 90 | + t.Helper() |
| 91 | + dir := t.TempDir() |
| 92 | + for i, d := range ddl { |
| 93 | + require.NoError(t, os.WriteFile(filepath.Join(dir, fmt.Sprintf("ddl_%d.sql", i)), []byte(d), 0644)) |
| 94 | + } |
| 95 | + return dir |
| 96 | +} |
| 97 | + |
| 98 | +func tempDsnDArg(pgEngine *pgengine.Engine, argName string, ddl []string) dArgGenerator { |
| 99 | + return func(t *testing.T) []string { |
| 100 | + t.Helper() |
| 101 | + db := tempDbWithSchema(t, pgEngine, ddl) |
| 102 | + return []string{"--" + argName, db.GetDSN()} |
| 103 | + } |
| 104 | +} |
| 105 | + |
| 106 | +func tempDbWithSchema(t *testing.T, pgEngine *pgengine.Engine, ddl []string) *pgengine.DB { |
| 107 | + t.Helper() |
| 108 | + db, err := pgEngine.CreateDatabase() |
| 109 | + require.NoError(t, err) |
| 110 | + t.Cleanup(func() { |
| 111 | + require.NoError(t, db.DropDB()) |
| 112 | + }) |
| 113 | + dbPool, err := sql.Open("pgx", db.GetDSN()) |
| 114 | + require.NoError(t, err) |
| 115 | + defer func() { |
| 116 | + require.NoError(t, dbPool.Close()) |
| 117 | + }() |
| 118 | + for _, d := range ddl { |
| 119 | + _, err := dbPool.Exec(d) |
| 120 | + require.NoError(t, err) |
| 121 | + } |
| 122 | + return db |
| 123 | +} |
| 124 | + |
| 125 | +func tempSetPqEnvVarsForDb(t *testing.T, db *pgengine.DB) { |
| 126 | + t.Helper() |
| 127 | + tempSetEnvVar(t, "PGHOST", db.GetConnOpts()[pgengine.ConnectionOptionHost]) |
| 128 | + tempSetEnvVar(t, "PGPORT", db.GetConnOpts()[pgengine.ConnectionOptionPort]) |
| 129 | + tempSetEnvVar(t, "PGUSER", db.GetConnOpts()[pgengine.ConnectionOptionUser]) |
| 130 | + tempSetEnvVar(t, "PGDATABASE", db.GetName()) |
| 131 | +} |
| 132 | + |
| 133 | +func tempSetEnvVar(t *testing.T, k, v string) { |
| 134 | + t.Helper() |
| 135 | + require.NoError(t, os.Setenv(k, v)) |
| 136 | + t.Cleanup(func() { |
| 137 | + require.NoError(t, os.Unsetenv(k)) |
| 138 | + }) |
| 139 | +} |
| 140 | + |
| 141 | +type cmdOutput struct { |
| 142 | + // stdOut is the stdout output of the command. |
| 143 | + stdOut string |
| 144 | + // stdErr is the stderr output of the command. This is DINSTCT |
| 145 | + // from the error returned by cmd.RunE. |
| 146 | + stdErr string |
| 147 | + // err is the err returned by cmd.RunE. |
| 148 | + err error |
| 149 | +} |
| 150 | + |
| 151 | +func runCmd(args []string) cmdOutput { |
| 152 | + rootCmd := buildRootCmd() |
| 153 | + rootCmd.SetArgs(args) |
| 154 | + stdOut := &bytes.Buffer{} |
| 155 | + rootCmd.SetOut(stdOut) |
| 156 | + stdErr := &bytes.Buffer{} |
| 157 | + rootCmd.SetErr(stdErr) |
| 158 | + |
| 159 | + err := rootCmd.Execute() |
| 160 | + |
| 161 | + return cmdOutput{ |
| 162 | + stdOut: stdOut.String(), |
| 163 | + stdErr: stdErr.String(), |
| 164 | + err: err, |
| 165 | + } |
| 166 | +} |
| 167 | + |
| 168 | +func TestCmdTestSuite(t *testing.T) { |
| 169 | + suite.Run(t, new(cmdTestSuite)) |
| 170 | +} |
0 commit comments