feat(tfstated): begin implementing tests

This commit is contained in:
Julien Dessaux 2024-10-04 00:22:49 +02:00
parent 3d74311931
commit 7ec826921c
Signed by: adyxax
GPG key ID: F92E51B86E07177E
7 changed files with 249 additions and 10 deletions

40
cmd/tfstated/get_test.go Normal file
View 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))
}
}
})
}
}

View 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))
}
})
}

View file

@ -26,10 +26,11 @@ func run(
ctx context.Context,
config *Config,
db *database.DB,
args []string,
//args []string,
getenv func(string) string,
stdin io.Reader,
stdout, stderr io.Writer,
//stdin io.Reader,
//stdout io.Writer,
stderr io.Writer,
) error {
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
defer cancel()
@ -55,7 +56,7 @@ func run(
go func() {
log.Printf("listening on %s\n", httpServer.Addr)
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
@ -67,7 +68,7 @@ func run(
shutdownCtx, cancel := context.WithTimeout(shutdownCtx, 10*time.Second)
defer cancel()
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()
@ -104,10 +105,11 @@ func main() {
ctx,
&config,
db,
os.Args,
//os.Args,
os.Getenv,
os.Stdin,
os.Stdout, os.Stderr,
//os.Stdin,
//os.Stdout,
os.Stderr,
); err != nil {
fmt.Fprintf(os.Stderr, "%+v\n", err)
os.Exit(1)

131
cmd/tfstated/main_test.go Normal file
View 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)
}
}
}

View file

@ -20,7 +20,7 @@ func handlePost(db *database.DB) http.Handler {
id := r.URL.Query().Get("ID")
data, err := io.ReadAll(r.Body)
if err != nil {
if err != nil || len(data) == 0 {
_ = errorResponse(w, http.StatusBadRequest, err)
return
}

49
cmd/tfstated/post_test.go Normal file
View 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
View file

@ -1,5 +1,5 @@
module git.adyxax.org/adyxax/tfstated
go 1.23.1
go 1.23.2
require github.com/mattn/go-sqlite3 v1.14.23