183 lines
5.9 KiB
Go
183 lines
5.9 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"box-manifest/server/internal/tree"
|
|
_ "modernc.org/sqlite"
|
|
)
|
|
|
|
func TestNodeLifecycleThroughHTTP(t *testing.T) {
|
|
handler := newTestHandler(t)
|
|
|
|
create := request(t, handler, http.MethodPost, "/api/nodes", `{"name":"Pens","quantity":12}`)
|
|
if create.Code != http.StatusCreated {
|
|
t.Fatalf("create: got %d: %s", create.Code, create.Body.String())
|
|
}
|
|
var node tree.Node
|
|
if err := json.Unmarshal(create.Body.Bytes(), &node); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if node.ParentID == nil || *node.ParentID != tree.UnsortedID {
|
|
t.Fatalf("expected default Unsorted parent, got %#v", node)
|
|
}
|
|
if node.LookupCode == "" {
|
|
t.Fatal("created node has no lookup code")
|
|
}
|
|
lookup := request(t, handler, http.MethodGet, "/api/nodes/by-code/"+node.LookupCode, "")
|
|
if lookup.Code != http.StatusOK || !bytes.Contains(lookup.Body.Bytes(), []byte(node.ID)) {
|
|
t.Fatalf("lookup: got %d: %s", lookup.Code, lookup.Body.String())
|
|
}
|
|
canonical := request(t, handler, http.MethodGet, "/n/"+node.LookupCode, "")
|
|
if canonical.Code != http.StatusSeeOther || canonical.Header().Get("Location") != "/?code="+node.LookupCode {
|
|
t.Fatalf("canonical route: got %d, %q", canonical.Code, canonical.Header().Get("Location"))
|
|
}
|
|
|
|
update := request(t, handler, http.MethodPatch, "/api/nodes/"+node.ID, `{"quantity":null}`)
|
|
if update.Code != http.StatusOK {
|
|
t.Fatalf("update: got %d: %s", update.Code, update.Body.String())
|
|
}
|
|
if err := json.Unmarshal(update.Body.Bytes(), &node); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if node.Quantity != nil {
|
|
t.Fatalf("expected quantity to be cleared, got %v", *node.Quantity)
|
|
}
|
|
|
|
treeResponse := request(t, handler, http.MethodGet, "/api/tree", "")
|
|
if treeResponse.Code != http.StatusOK {
|
|
t.Fatalf("tree: got %d: %s", treeResponse.Code, treeResponse.Body.String())
|
|
}
|
|
|
|
deleteResponse := request(t, handler, http.MethodDelete, "/api/nodes/"+node.ID, "")
|
|
if deleteResponse.Code != http.StatusNoContent {
|
|
t.Fatalf("delete: got %d: %s", deleteResponse.Code, deleteResponse.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestBulkCreateThroughHTTP(t *testing.T) {
|
|
handler := newTestHandler(t)
|
|
response := request(
|
|
t,
|
|
handler,
|
|
http.MethodPost,
|
|
"/api/nodes/bulk",
|
|
`{"parentId":"unsorted","names":["Hammer","Screws"]}`,
|
|
)
|
|
if response.Code != http.StatusCreated {
|
|
t.Fatalf("bulk create: got %d: %s", response.Code, response.Body.String())
|
|
}
|
|
var body struct {
|
|
Nodes []tree.Node `json:"nodes"`
|
|
}
|
|
if err := json.Unmarshal(response.Body.Bytes(), &body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(body.Nodes) != 2 || body.Nodes[0].Name != "Hammer" || body.Nodes[1].Name != "Screws" {
|
|
t.Fatalf("unexpected response: %#v", body.Nodes)
|
|
}
|
|
}
|
|
|
|
func TestBulkMoveAndDeleteThroughHTTP(t *testing.T) {
|
|
handler := newTestHandler(t)
|
|
first := createNodeThroughHTTP(t, handler, tree.UnsortedID, "first")
|
|
second := createNodeThroughHTTP(t, handler, tree.UnsortedID, "second")
|
|
target := createNodeThroughHTTP(t, handler, tree.RootID, "target")
|
|
|
|
moveBody := `{"nodeIds":["` + first.ID + `","` + second.ID + `"],"targetParentId":"` + target.ID + `","childHandling":"WITH_SUBTREE"}`
|
|
moveResponse := request(t, handler, http.MethodPost, "/api/nodes/bulk/move", moveBody)
|
|
if moveResponse.Code != http.StatusOK {
|
|
t.Fatalf("bulk move: got %d: %s", moveResponse.Code, moveResponse.Body.String())
|
|
}
|
|
|
|
deleteBody := `{"nodeIds":["` + first.ID + `","` + second.ID + `"]}`
|
|
deleteResponse := request(t, handler, http.MethodPost, "/api/nodes/bulk/delete", deleteBody)
|
|
if deleteResponse.Code != http.StatusNoContent {
|
|
t.Fatalf("bulk delete: got %d: %s", deleteResponse.Code, deleteResponse.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestTreeImportPreviewAndCommitThroughHTTP(t *testing.T) {
|
|
handler := newTestHandler(t)
|
|
preview := request(
|
|
t,
|
|
handler,
|
|
http.MethodPost,
|
|
"/api/imports/tree/preview",
|
|
`{"parentId":"unsorted","manifest":{"nodes":[{"name":"Box A","children":[{"name":"Cable","quantity":2}]}]}}`,
|
|
)
|
|
if preview.Code != http.StatusOK {
|
|
t.Fatalf("preview: got %d: %s", preview.Code, preview.Body.String())
|
|
}
|
|
var previewBody struct {
|
|
PlanID string `json:"planId"`
|
|
}
|
|
if err := json.Unmarshal(preview.Body.Bytes(), &previewBody); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
commit := request(
|
|
t,
|
|
handler,
|
|
http.MethodPost,
|
|
"/api/imports/tree/commit",
|
|
`{"planId":"`+previewBody.PlanID+`"}`,
|
|
)
|
|
if commit.Code != http.StatusCreated {
|
|
t.Fatalf("commit: got %d: %s", commit.Code, commit.Body.String())
|
|
}
|
|
treeResponse := request(t, handler, http.MethodGet, "/api/tree", "")
|
|
if !bytes.Contains(treeResponse.Body.Bytes(), []byte(`"name":"Box A"`)) ||
|
|
!bytes.Contains(treeResponse.Body.Bytes(), []byte(`"name":"Cable"`)) {
|
|
t.Fatalf("imported tree is missing: %s", treeResponse.Body.String())
|
|
}
|
|
}
|
|
|
|
func newTestHandler(t *testing.T) http.Handler {
|
|
t.Helper()
|
|
db, err := sql.Open("sqlite", ":memory:")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { db.Close() })
|
|
store := tree.NewStore(db)
|
|
if err := store.Initialize(context.Background()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return New(store)
|
|
}
|
|
|
|
func request(t *testing.T, handler http.Handler, method, path, body string) *httptest.ResponseRecorder {
|
|
t.Helper()
|
|
request := httptest.NewRequest(method, path, bytes.NewBufferString(body))
|
|
if body != "" {
|
|
request.Header.Set("Content-Type", "application/json")
|
|
}
|
|
response := httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
return response
|
|
}
|
|
|
|
func createNodeThroughHTTP(t *testing.T, handler http.Handler, parentID, name string) tree.Node {
|
|
t.Helper()
|
|
body, err := json.Marshal(map[string]any{"parentId": parentID, "name": name})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
response := request(t, handler, http.MethodPost, "/api/nodes", string(body))
|
|
if response.Code != http.StatusCreated {
|
|
t.Fatalf("create %s: got %d: %s", name, response.Code, response.Body.String())
|
|
}
|
|
var node tree.Node
|
|
if err := json.Unmarshal(response.Body.Bytes(), &node); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return node
|
|
}
|