feat(tfstated): begin implementing tests
This commit is contained in:
parent
3d74311931
commit
7ec826921c
7 changed files with 249 additions and 10 deletions
40
cmd/tfstated/get_test.go
Normal file
40
cmd/tfstated/get_test.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGet(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
method string
|
||||||
|
uri *url.URL
|
||||||
|
body io.Reader
|
||||||
|
expect string
|
||||||
|
status int
|
||||||
|
msg string
|
||||||
|
}{
|
||||||
|
{"GET", &url.URL{Path: "/"}, nil, "", http.StatusBadRequest, "/"},
|
||||||
|
{"GET", &url.URL{Path: "/non_existent_get"}, nil, "", http.StatusNotFound, "non existent"},
|
||||||
|
{"POST", &url.URL{Path: "/test_get"}, strings.NewReader("the_test_get"), "", http.StatusOK, "/test_get"},
|
||||||
|
{"GET", &url.URL{Path: "/test_get"}, nil, "the_test_get", http.StatusOK, "/test_get"},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
runHTTPRequest(tt.method, tt.uri, tt.body, func(r *http.Response, err error) {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed %s with error: %+v", tt.method, err)
|
||||||
|
} else if r.StatusCode != tt.status {
|
||||||
|
t.Fatalf("%s %s should %s, got %s", tt.method, tt.msg, http.StatusText(tt.status), http.StatusText(r.StatusCode))
|
||||||
|
} else if tt.expect != "" {
|
||||||
|
if body, err := io.ReadAll(r.Body); err != nil {
|
||||||
|
t.Fatalf("failed to read body with error: %+v", err)
|
||||||
|
} else if strings.Compare(string(body), tt.expect) != 0 {
|
||||||
|
t.Fatalf("%s should have returned \"%s\", got %s", tt.method, tt.expect, string(body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
17
cmd/tfstated/healthz_test.go
Normal file
17
cmd/tfstated/healthz_test.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHealthz(t *testing.T) {
|
||||||
|
runHTTPRequest("GET", &url.URL{Path: "/healthz"}, nil, func(r *http.Response, err error) {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed healthcheck with error: %+v", err)
|
||||||
|
} else if r.StatusCode != http.StatusOK {
|
||||||
|
t.Fatalf("healthcheck should succeed, got %s", http.StatusText(r.StatusCode))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -26,10 +26,11 @@ func run(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
config *Config,
|
config *Config,
|
||||||
db *database.DB,
|
db *database.DB,
|
||||||
args []string,
|
//args []string,
|
||||||
getenv func(string) string,
|
getenv func(string) string,
|
||||||
stdin io.Reader,
|
//stdin io.Reader,
|
||||||
stdout, stderr io.Writer,
|
//stdout io.Writer,
|
||||||
|
stderr io.Writer,
|
||||||
) error {
|
) error {
|
||||||
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
|
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
@ -55,7 +56,7 @@ func run(
|
||||||
go func() {
|
go func() {
|
||||||
log.Printf("listening on %s\n", httpServer.Addr)
|
log.Printf("listening on %s\n", httpServer.Addr)
|
||||||
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
fmt.Fprintf(os.Stderr, "error listening and serving: %+v\n", err)
|
fmt.Fprintf(stderr, "error listening and serving: %+v\n", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
@ -67,7 +68,7 @@ func run(
|
||||||
shutdownCtx, cancel := context.WithTimeout(shutdownCtx, 10*time.Second)
|
shutdownCtx, cancel := context.WithTimeout(shutdownCtx, 10*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if err := httpServer.Shutdown(shutdownCtx); err != nil {
|
if err := httpServer.Shutdown(shutdownCtx); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "error shutting down http server: %+v\n", err)
|
fmt.Fprintf(stderr, "error shutting down http server: %+v\n", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
@ -104,10 +105,11 @@ func main() {
|
||||||
ctx,
|
ctx,
|
||||||
&config,
|
&config,
|
||||||
db,
|
db,
|
||||||
os.Args,
|
//os.Args,
|
||||||
os.Getenv,
|
os.Getenv,
|
||||||
os.Stdin,
|
//os.Stdin,
|
||||||
os.Stdout, os.Stderr,
|
//os.Stdout,
|
||||||
|
os.Stderr,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "%+v\n", err)
|
fmt.Fprintf(os.Stderr, "%+v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
131
cmd/tfstated/main_test.go
Normal file
131
cmd/tfstated/main_test.go
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.adyxax.org/adyxax/tfstated/pkg/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
var baseURI = &url.URL{
|
||||||
|
Host: "127.0.0.1:8081",
|
||||||
|
Path: "/",
|
||||||
|
Scheme: "http",
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
ctx := context.Background()
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
config := Config{
|
||||||
|
Host: "127.0.0.1",
|
||||||
|
Port: "8081",
|
||||||
|
}
|
||||||
|
_ = os.Remove("./test.db")
|
||||||
|
db, err := database.NewDB(ctx, "./test.db")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%+v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
getenv := func(key string) string {
|
||||||
|
switch key {
|
||||||
|
case "DATA_ENCRYPTION_KEY":
|
||||||
|
return "hP3ZSCnY3LMgfTQjwTaGrhKwdA0yXMXIfv67OJnntqM="
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go run(
|
||||||
|
ctx,
|
||||||
|
&config,
|
||||||
|
db,
|
||||||
|
getenv,
|
||||||
|
os.Stderr,
|
||||||
|
)
|
||||||
|
err = waitForReady(ctx, 5*time.Second, "http://127.0.0.1:8081/healthz")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%+v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := m.Run()
|
||||||
|
|
||||||
|
cancel()
|
||||||
|
err = db.Close()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%+v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
_ = os.Remove("./test.db")
|
||||||
|
|
||||||
|
os.Exit(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runHTTPRequest(method string, uriRef *url.URL, body io.Reader, testFunc func(*http.Response, error)) {
|
||||||
|
uri := baseURI.ResolveReference(uriRef)
|
||||||
|
client := http.Client{}
|
||||||
|
req, err := http.NewRequest(method, uri.String(), body)
|
||||||
|
if err != nil {
|
||||||
|
testFunc(nil, fmt.Errorf("failed to create request: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
testFunc(nil, fmt.Errorf("failed to do request: %w\n", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
testFunc(resp, nil)
|
||||||
|
_ = resp.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitForReady calls the specified endpoint until it gets a 200
|
||||||
|
// response or until the context is cancelled or the timeout is
|
||||||
|
// reached.
|
||||||
|
func waitForReady(
|
||||||
|
ctx context.Context,
|
||||||
|
timeout time.Duration,
|
||||||
|
endpoint string,
|
||||||
|
) error {
|
||||||
|
client := http.Client{}
|
||||||
|
startTime := time.Now()
|
||||||
|
for {
|
||||||
|
req, err := http.NewRequestWithContext(
|
||||||
|
ctx,
|
||||||
|
http.MethodGet,
|
||||||
|
endpoint,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error making request: %s\n", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if resp.StatusCode == http.StatusOK {
|
||||||
|
fmt.Println("Endpoint is ready!")
|
||||||
|
resp.Body.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
if time.Since(startTime) >= timeout {
|
||||||
|
return fmt.Errorf("timeout reached while waiting for endpoint")
|
||||||
|
}
|
||||||
|
// wait a little while between checks
|
||||||
|
time.Sleep(250 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,7 @@ func handlePost(db *database.DB) http.Handler {
|
||||||
id := r.URL.Query().Get("ID")
|
id := r.URL.Query().Get("ID")
|
||||||
|
|
||||||
data, err := io.ReadAll(r.Body)
|
data, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil || len(data) == 0 {
|
||||||
_ = errorResponse(w, http.StatusBadRequest, err)
|
_ = errorResponse(w, http.StatusBadRequest, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
49
cmd/tfstated/post_test.go
Normal file
49
cmd/tfstated/post_test.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPost(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
method string
|
||||||
|
uri *url.URL
|
||||||
|
body io.Reader
|
||||||
|
expect string
|
||||||
|
status int
|
||||||
|
msg string
|
||||||
|
}{
|
||||||
|
{"POST", &url.URL{Path: "/"}, nil, "", http.StatusBadRequest, "/"},
|
||||||
|
{"POST", &url.URL{Path: "/test_post"}, nil, "", http.StatusBadRequest, "without a body"},
|
||||||
|
{"POST", &url.URL{Path: "/test_post"}, strings.NewReader("the_test_post"), "", http.StatusOK, "without lock ID in query string"},
|
||||||
|
{"GET", &url.URL{Path: "/test_post"}, nil, "the_test_post", http.StatusOK, "/test_post"},
|
||||||
|
{"POST", &url.URL{Path: "/test_post", RawQuery: "ID=test_post"}, strings.NewReader("the_test_post2"), "", http.StatusConflict, "with a lock ID on an unlocked state"},
|
||||||
|
{"GET", &url.URL{Path: "/test_post"}, nil, "the_test_post", http.StatusOK, "/test_post"},
|
||||||
|
{"LOCK", &url.URL{Path: "/test_post"}, strings.NewReader("{\"ID\":\"test_post_lock\"}"), "", http.StatusOK, "/test_post"},
|
||||||
|
{"POST", &url.URL{Path: "/test_post", RawQuery: "ID=test_post_invalid"}, strings.NewReader("the_test_post3"), "", http.StatusConflict, "with a wrong lock ID on a locked state"},
|
||||||
|
{"GET", &url.URL{Path: "/test_post"}, nil, "the_test_post", http.StatusOK, "/test_post"},
|
||||||
|
{"POST", &url.URL{Path: "/test_post", RawQuery: "ID=test_post_lock"}, strings.NewReader("the_test_post4"), "", http.StatusOK, "with a correct lock ID on a locked state"},
|
||||||
|
{"GET", &url.URL{Path: "/test_post"}, nil, "the_test_post4", http.StatusOK, "/test_post"},
|
||||||
|
{"POST", &url.URL{Path: "/test_post"}, strings.NewReader("the_test_post5"), "", http.StatusOK, "without lock ID in query string on a locked state"},
|
||||||
|
{"GET", &url.URL{Path: "/test_post"}, nil, "the_test_post5", http.StatusOK, "/test_post"},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
runHTTPRequest(tt.method, tt.uri, tt.body, func(r *http.Response, err error) {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed %s with error: %+v", tt.method, err)
|
||||||
|
} else if r.StatusCode != tt.status {
|
||||||
|
t.Fatalf("%s %s should %s, got %s", tt.method, tt.msg, http.StatusText(tt.status), http.StatusText(r.StatusCode))
|
||||||
|
} else if tt.expect != "" {
|
||||||
|
if body, err := io.ReadAll(r.Body); err != nil {
|
||||||
|
t.Fatalf("failed to read body with error: %+v", err)
|
||||||
|
} else if strings.Compare(string(body), tt.expect) != 0 {
|
||||||
|
t.Fatalf("%s should have returned \"%s\", got %s", tt.method, tt.expect, string(body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
2
go.mod
2
go.mod
|
@ -1,5 +1,5 @@
|
||||||
module git.adyxax.org/adyxax/tfstated
|
module git.adyxax.org/adyxax/tfstated
|
||||||
|
|
||||||
go 1.23.1
|
go 1.23.2
|
||||||
|
|
||||||
require github.com/mattn/go-sqlite3 v1.14.23
|
require github.com/mattn/go-sqlite3 v1.14.23
|
||||||
|
|
Loading…
Add table
Reference in a new issue