package api_test import ( "context" "encoding/base64" "encoding/json" "io" "net/http" "net/http/httptest" "path/filepath" "strings" "testing" "wh/api" "wh/config" "wh/store" "wh/web" ) // newSite starts the API with the web front end mounted, as cmd/server does. func newSite(t *testing.T) (*httptest.Server, config.Config) { t.Helper() cfg := config.Default() st, err := store.Open("sqlite:" + filepath.Join(t.TempDir(), "t.db")) if err != nil { t.Fatal(err) } t.Cleanup(func() { st.Close() }) if err := st.Migrate(context.Background()); err != nil { t.Fatal(err) } mux := api.New(st, cfg) mux.Handle("/", web.Handler()) srv := httptest.NewServer(mux) t.Cleanup(srv.Close) return srv, cfg } func get(t *testing.T, url string) (*http.Response, string) { t.Helper() resp, err := http.Get(url) if err != nil { t.Fatal(err) } defer resp.Body.Close() b, _ := io.ReadAll(resp.Body) return resp, string(b) } func TestInfoAndAssemble(t *testing.T) { srv, cfg := newSite(t) _, body := get(t, srv.URL+"/info") var info struct { ProgramBytes int `json:"program_bytes"` CommBytes int `json:"comm_bytes"` Ores []string `json:"ores"` } if err := json.Unmarshal([]byte(body), &info); err != nil { t.Fatal(err) } if info.ProgramBytes != cfg.ProgramBytes || info.CommBytes != cfg.CommBytes || len(info.Ores) != 4 { t.Fatalf("info = %+v", info) } post := func(src string) (int, map[string]any) { resp, err := http.Post(srv.URL+"/assemble", "text/plain", strings.NewReader(src)) if err != nil { t.Fatal(err) } defer resp.Body.Close() var m map[string]any json.NewDecoder(resp.Body).Decode(&m) return resp.StatusCode, m } code, m := post("ldi r1, 5\nhalt") if code != 200 || m["size"].(float64) != 8 || m["fits"] != true { t.Fatalf("assemble ok case: %d %v", code, m) } if raw, _ := base64.StdEncoding.DecodeString(m["program"].(string)); len(raw) != 8 { t.Fatalf("program bytes = %d", len(raw)) } code, m = post("ldi r1, 5\nbogus r1") if code != 400 || !strings.Contains(m["error"].(string), "line 2") { t.Fatalf("assemble error case: %d %v", code, m) } // Too big for the belt: assembles, but reports that it does not fit. code, m = post(strings.Repeat("nop\n", cfg.ProgramBytes/4+1)) if code != 200 || m["fits"] != false { t.Fatalf("oversize case: %d %v", code, m) } } func TestWebAssets(t *testing.T) { srv, _ := newSite(t) for path, want := range map[string]string{ "/": "