github.com/omnigres/cli@v0.1.4/cmd/test.go (about) 1 package cmd 2 3 import ( 4 "context" 5 "database/sql" 6 "encoding/json" 7 "fmt" 8 cloudevents "github.com/cloudevents/sdk-go/v2" 9 "os" 10 "path" 11 "time" 12 13 "github.com/charmbracelet/log" 14 "github.com/omnigres/cli/orb" 15 "github.com/relvacode/iso8601" 16 "github.com/spf13/cobra" 17 ) 18 19 var testCmd = &cobra.Command{ 20 Use: "test", 21 Short: "Test orbs", 22 Run: func(cmd *cobra.Command, args []string) { 23 var cluster orb.OrbCluster 24 var err error 25 cluster, err = getOrbCluster() 26 if err != nil { 27 log.Fatal(err) 28 } 29 30 log.Debug("Workspace", "workspace", workspace) 31 cwd, err := os.Getwd() 32 if err != nil { 33 log.Fatal(err) 34 } 35 36 orbs, err := currentOrbs(cluster, cwd) 37 if err != nil { 38 log.Fatal(err) 39 } 40 41 ctx := context.Background() 42 log.Debug("Assembling orbs src", "orbs", orbs) 43 44 t := time.Now().Format("20060102150405") 45 nameForTestDatabase := func(orbName string) string { 46 return fmt.Sprintf("%s_%s_%s", orbName, "test", t) 47 } 48 49 err = testOrbs(ctx, cluster, orbs, nameForTestDatabase) 50 51 if err != nil { 52 log.Fatal(err) 53 } 54 }, 55 } 56 57 func testOrbs( 58 ctx context.Context, 59 cluster orb.OrbCluster, 60 orbs []string, 61 databaseForOrb func(string) string, 62 ) (err error) { 63 64 var testTarget, testRunner *sql.DB 65 testRunner, err = cluster.Connect(ctx, "omnigres") 66 if err != nil { 67 log.Error("Could not connect to orb. Ensure the docker container is running, perhaps 'omnigres start' will fix it.") 68 return 69 } 70 71 testOrb := func(orbName string) error { 72 dbName := databaseForOrb(orbName) 73 testTarget, err = cluster.Connect(ctx, dbName) 74 if err != nil { 75 return err 76 } 77 log.Debug("Testing orb", "orbName", orbName, "dbName", dbName) 78 79 _, err = testRunner.ExecContext(ctx, fmt.Sprintf(`create database %q`, dbName)) 80 if err != nil { 81 log.Fatal(err) 82 } 83 _, err = testRunner.ExecContext( 84 ctx, 85 "update pg_database set datistemplate = true where datname = $1", 86 dbName, 87 ) 88 if err != nil { 89 return err 90 } 91 cleanTestRunner := func() { 92 // remove istemplate so we can drop the database 93 _, err = testRunner.ExecContext( 94 ctx, 95 "update pg_database set datistemplate = false where datname = $1", 96 dbName, 97 ) 98 if err != nil { 99 log.Fatal(err) 100 } 101 102 _, err = testRunner.ExecContext( 103 ctx, 104 fmt.Sprintf("drop database \"%s\"", dbName), 105 ) 106 if err != nil { 107 log.Fatal(err) 108 } 109 } 110 defer cleanTestRunner() 111 112 orbSource := path.Join(orbName, "src") 113 assembleSchema(ctx, testRunner, orbSource, dbName) 114 115 _, err = testTarget.ExecContext(ctx, "create extension omni_test cascade") 116 if err != nil { 117 return err 118 } 119 testTarget.Close() 120 121 conn, err := testRunner.Conn(ctx) 122 if err != nil { 123 return err 124 } 125 defer conn.Close() 126 127 // assemble tests in target db 128 orbTestSource := path.Join(orbName, "tests") 129 assembleSchema(ctx, testRunner, orbTestSource, dbName) 130 131 // run tests 132 log.Infof("") 133 log.Infof("=== Running tests for %s ===", orbName) 134 135 err = setupCloudevents(ctx, conn) 136 if err != nil { 137 return err 138 } 139 140 testRows, err := conn.QueryContext( 141 ctx, 142 `select name, description, error_message from omni_test.run_tests($1)`, 143 dbName, 144 ) 145 if err != nil { 146 return err 147 } 148 defer testRows.Close() 149 150 for testRows.Next() { 151 var name, description, error_message sql.NullString 152 err = testRows.Scan(&name, &description, &error_message) 153 if err != nil { 154 return err 155 } 156 } 157 log.Info("===================================================================") 158 159 return nil 160 } 161 162 for _, orbName := range orbs { 163 err := testOrb(orbName) 164 if err != nil { 165 log.Error(err) 166 } 167 } 168 return 169 } 170 171 type testPassed struct { 172 Name string `json:"name"` 173 Description string `json:"description"` 174 StartTime iso8601.Time `json:"start_time"` 175 EndTime iso8601.Time `json:"end_time"` 176 } 177 178 type testFailed struct { 179 Name string `json:"name"` 180 Description string `json:"description"` 181 StartTime iso8601.Time `json:"start_time"` 182 EndTime iso8601.Time `json:"end_time"` 183 Error string `json:"error"` 184 } 185 186 func init() { 187 rootCmd.AddCommand(testCmd) 188 189 handler := cloudeventHandler{ 190 Callback: func(e *cloudevents.Event) { 191 switch e.Type() { 192 case "org.omnigres.omni_test.test.passed.v1": 193 var msg testPassed 194 err := json.Unmarshal(e.Data(), &msg) 195 if err == nil { 196 log.Infof("✅ - %s (%s) [%s]", msg.Name, msg.Description, msg.EndTime.Sub(msg.StartTime.Time).String()) 197 } else { 198 log.Error(err) 199 } 200 case "org.omnigres.omni_test.test.failed.v1": 201 var msg testFailed 202 err := json.Unmarshal(e.Data(), &msg) 203 if err == nil { 204 log.Infof("🔴 - %s (%s) [%s]: %s", msg.Name, msg.Description, msg.EndTime.Sub(msg.StartTime.Time).String(), msg.Error) 205 } else { 206 log.Error(err) 207 } 208 default: 209 } 210 }, 211 } 212 cloudeventHandlers = append(cloudeventHandlers, handler) 213 }