Skip to content

Commit

Permalink
Now with working tests
Browse files Browse the repository at this point in the history
  • Loading branch information
chriskuehl committed Sep 2, 2024
1 parent 27bbd71 commit a7f0c8b
Show file tree
Hide file tree
Showing 11 changed files with 217 additions and 98 deletions.
9 changes: 9 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install sass
run: |
wget -O /tmp/sass.tar.gz https://github.com/sass/dart-sass/releases/download/1.77.8/dart-sass-1.77.8-linux-x64.tar.gz
cd /tmp
sha256sum -c <<< "b4a46d1b47fcfed0f38e02c3a5383d953dcc94e28fc67fdee517e5bde25eaf71 *sass.tar.gz"
tar -xvf /tmp/sass.tar.gz
echo /tmp/dart-sass >> $GITHUB_PATH
- name: Build
run: make minimal
- name: Run tests
run: make test
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.PHONY: minimal
minimal: bin/server bin/fpb bin/fput assets settings.py install-hooks
minimal: bin/server bin/fpb bin/fput assets

.PHONY: bin/server
bin/server:
Expand Down
48 changes: 5 additions & 43 deletions cmd/server/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,15 @@ package main
import (
"bytes"
"context"
"fmt"
"net/http"
"strconv"
"strings"
"testing"
"time"
)

const port = 17491

func waitForReady(ctx context.Context, timeout time.Duration) error {
client := http.Client{}
startTime := time.Now()
for {
req, err := http.NewRequestWithContext(
ctx,
http.MethodGet,
fmt.Sprintf("http://localhost:%d/healthz", port),
nil,
)
if err != nil {
return fmt.Errorf("creating 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("/healthz is ready!")
resp.Body.Close()
return nil
}
resp.Body.Close()
"github.com/chriskuehl/fluffy/testfunc"
)

select {
case <-ctx.Done():
return ctx.Err()
default:
if time.Since(startTime) >= timeout {
return fmt.Errorf("timeout reached while waiting for endpoint")
}
time.Sleep(250 * time.Millisecond)
}
}
}
const port = 14921

func TestIntegration(t *testing.T) {
ctx := context.Background()
Expand All @@ -63,8 +25,8 @@ func TestIntegration(t *testing.T) {
}
close(done)
}()
if err := waitForReady(ctx, 5*time.Second); err != nil {
t.Errorf("unexpected error: %v", err)
if err := testfunc.WaitForReady(ctx, 5*time.Second, port); err != nil {
t.Fatalf("unexpected error: %v", err)
}
cancel()
<-done
Expand Down
33 changes: 33 additions & 0 deletions server/server_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package server_test

import (
"fmt"
"io"
"net/http"
"testing"

"github.com/chriskuehl/fluffy/testfunc"
)

func TestHealthz(t *testing.T) {
ts := testfunc.RunningServer(t, testfunc.NewConfig())
defer ts.Cleanup()

resp, err := http.Get(fmt.Sprintf("http://localhost:%d/healthz", ts.Port))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("unexpected status code: got %d, want %d", resp.StatusCode, http.StatusOK)
}
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
want := "ok\n"
body := string(bodyBytes)
if body != want {
t.Errorf("unexpected body: got %q, want %q", body, want)
}
}
43 changes: 43 additions & 0 deletions server/views_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package server_test

import (
"fmt"
"io"
"net/http"
"strings"
"testing"

"github.com/chriskuehl/fluffy/testfunc"
)

func TestIndex(t *testing.T) {
ts := testfunc.RunningServer(t, testfunc.NewConfig())
defer ts.Cleanup()

resp, err := http.Get(fmt.Sprintf("http://localhost:%d/", ts.Port))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("unexpected status code: got %d, want %d", resp.StatusCode, http.StatusOK)
}
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
body := string(bodyBytes)

wantContent := []string{
`<html class="page-index`,
`<title>fluffy</title>`,
`var icons = {"7z":"http://`,
`var maxUploadSize = 10485760 ;`,
`<a class="report" href="mailto:[email protected]">Report Abuse</a>`,
}
for _, want := range wantContent {
if !strings.Contains(body, want) {
t.Errorf("unexpected body: wanted to find %q but it is not present\nBody:\n%s", want, body)
}
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
126 changes: 126 additions & 0 deletions testfunc/integration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package testfunc

import (
"bytes"
"context"
"fmt"
"io"
"log/slog"
"net"
"net/http"
"sync"
"testing"
"time"

"github.com/chriskuehl/fluffy/server"
"github.com/chriskuehl/fluffy/server/logging"
)

func NewConfig() *server.Config {
c := server.NewConfig()
c.Version = "(test)"
return c
}

type TestServer struct {
Cleanup func()
Logs *bytes.Buffer
Port int
}

func RunningServer(t *testing.T, config *server.Config) TestServer {
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
var buf bytes.Buffer
port, done, err := run(t, ctx, &buf, config)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := WaitForReady(ctx, 5*time.Second, port); err != nil {
t.Fatalf("unexpected error: %v", err)
}
return TestServer{
Cleanup: sync.OnceFunc(func() {
cancel()
<-done
}),
Logs: &buf,
Port: port,
}
}

type serverState struct {
port int
err error
}

func run(t *testing.T, ctx context.Context, w io.Writer, config *server.Config) (int, chan struct{}, error) {
done := make(chan struct{})
logger := logging.NewSlogLogger(slog.New(slog.NewTextHandler(w, nil)))

handler, err := server.NewServer(logger, config)
if err != nil {
return 0, nil, fmt.Errorf("creating server: %w", err)
}

listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return 0, nil, fmt.Errorf("listening: %w", err)
}
httpServer := &http.Server{Handler: handler}
go func() {
if err := httpServer.Serve(listener); err != nil && err != http.ErrServerClosed {
t.Errorf("listening and serving: %v", err)
}
}()
go func() {
<-ctx.Done()
shutdownCtx := context.Background()
shutdownCtx, cancel := context.WithTimeout(shutdownCtx, 10*time.Second)
defer cancel()
if err := httpServer.Shutdown(shutdownCtx); err != nil {
t.Errorf("shutting down http server: %v", err)
}
close(done)
}()

return listener.Addr().(*net.TCPAddr).Port, done, nil
}

func WaitForReady(ctx context.Context, timeout time.Duration, port int) error {
client := http.Client{}
startTime := time.Now()
for {
req, err := http.NewRequestWithContext(
ctx,
http.MethodGet,
fmt.Sprintf("http://localhost:%d/healthz", port),
nil,
)
if err != nil {
return fmt.Errorf("creating 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("/healthz 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")
}
time.Sleep(250 * time.Millisecond)
}
}
}
54 changes: 0 additions & 54 deletions testing/__init__.py

This file was deleted.

0 comments on commit a7f0c8b

Please sign in to comment.