From b423adde4af5c7ca1fb5bf0929ad973acd35bdaa Mon Sep 17 00:00:00 2001 From: breel Date: Sat, 25 Jul 2020 20:20:12 -0600 Subject: [PATCH] Swagger for import-exoprt --- public/swagger/swagger-port.yaml | 50 ++++++++++++++ public/swagger/swagger-who.yaml | 32 +-------- public/swagger/swagger.yaml | 35 +++++++++- storage/entity/one.go | 9 +++ storage/entity/one_test.go | 62 +++++++++++++++++ view/json.go | 4 ++ view/port.go | 78 +++++++++++++++++++++ view/port_test.go | 112 +++++++++++++++++++++++++++++++ 8 files changed, 350 insertions(+), 32 deletions(-) create mode 100644 public/swagger/swagger-port.yaml create mode 100644 view/port.go create mode 100644 view/port_test.go diff --git a/public/swagger/swagger-port.yaml b/public/swagger/swagger-port.yaml new file mode 100644 index 0000000..6e8d4fb --- /dev/null +++ b/public/swagger/swagger-port.yaml @@ -0,0 +1,50 @@ +paths: + get: + tags: + - port + summary: "Export entities from a namespace" + parameters: + - $ref: "#/components/parameters/namespace" + responses: + 200: + $ref: "#/components/schemas/port" + + post: + tags: + - port + summary: "Import entities into a namespace" + parameters: + - $ref: "#/components/parameters/namespace" + requestBody: + $ref: "#/components/schemas/port" + responses: + 200: + content: + application/json: + schema: + type: object + properties: + namespace: + type: int + example: "5" + +components: + parameters: + namespace: + $ref: "./swagger.yaml#/components/parameters/namespace" + + schemas: + one: + $ref: "./swagger.yaml#/components/schemas/one" + + port: + description: "A {namespace:[entity]} json object" + content: + application/json: + schema: + type: object + properties: + namespace: + type: array + items: + $ref: "#/components/schemas/one" diff --git a/public/swagger/swagger-who.yaml b/public/swagger/swagger-who.yaml index 85f7c45..0d3eb3b 100644 --- a/public/swagger/swagger-who.yaml +++ b/public/swagger/swagger-who.yaml @@ -97,7 +97,7 @@ components: id: name: id in: query - description: "An entity's unique name" + description: "An entity's unique name, case insensitive for reads" schema: type: string @@ -134,35 +134,7 @@ components: schemas: one: - title: "One entity" - type: object - properties: - name: - type: string - example: "Jeff Snow" - type: - type: string - example: "doggo" - title: - type: string - example: "Meme Lord" - text: - type: string - example: "Lorem ipsum" - relationship: - type: string - example: "Good boi" - modified: - type: int - example: 8675309 - attachments: - type: object - additionalProperties: - type: string - connections: - type: object - additionalProperties: - type: object + $ref: "./swagger.yaml#/components/schemas/one" 200: content: diff --git a/public/swagger/swagger.yaml b/public/swagger/swagger.yaml index 2e28ce7..aea9480 100644 --- a/public/swagger/swagger.yaml +++ b/public/swagger/swagger.yaml @@ -19,6 +19,8 @@ paths: $ref: "./swagger-who.yaml#/paths" /register: $ref: "./swagger-register.yaml#/paths" + /port: + $ref: "./swagger-port.yaml#/paths" /__files__/{path}: $ref: "./swagger-files.yaml#/paths" @@ -43,13 +45,42 @@ components: ok: content: application/json: - schema: - type: object properties: status: type: string example: "ok" + one: + title: "One entity" + type: object + properties: + name: + type: string + example: "Jeff Snow" + type: + type: string + example: "doggo" + title: + type: string + example: "Meme Lord" + text: + type: string + example: "Lorem ipsum" + relationship: + type: string + example: "Good boi" + modified: + type: int + example: 8675309 + attachments: + type: object + additionalProperties: + type: string + connections: + type: object + additionalProperties: + type: object + securitySchemes: token: type: apiKey diff --git a/storage/entity/one.go b/storage/entity/one.go index d1a66d0..3223878 100644 --- a/storage/entity/one.go +++ b/storage/entity/one.go @@ -81,7 +81,16 @@ func (o One) MarshalBSON() ([]byte, error) { default: return nil, fmt.Errorf("bad connections type %T", m[Connections]) } + delete(connections, "") for k := range connections { + if k == "" { + continue + } + if o.Connections[k].Name == "" { + p := o.Connections[k] + p.Name = k + o.Connections[k] = p + } b, err := bson.Marshal(o.Connections[k]) if err != nil { return nil, err diff --git a/storage/entity/one_test.go b/storage/entity/one_test.go index ceb852e..991239c 100644 --- a/storage/entity/one_test.go +++ b/storage/entity/one_test.go @@ -71,3 +71,65 @@ func TestOneMarshalBSON(t *testing.T) { }) } } + +func TestOneMarshalBSONBadConnections(t *testing.T) { + t.Run("connections has an empty string for a key that should die", func(t *testing.T) { + input := One{Name: "hello", Connections: map[string]One{"": One{Name: "teehee"}}} + + b, err := bson.Marshal(input) + if err != nil { + t.Fatal(err) + } + + output := One{} + if err := bson.Unmarshal(b, &output); err != nil { + t.Fatal(err) + } + + if len(output.Connections) != 0 { + t.Fatal(output.Connections) + } + + input.Connections = nil + output.Connections = nil + input.Modified = 0 + output.Modified = 0 + + if fmt.Sprint(input) != fmt.Sprint(output) { + t.Fatal(input, output) + } + }) + + t.Run("connections has a key but empty name that should correct", func(t *testing.T) { + input := One{Name: "hello", Connections: map[string]One{"teehee": One{Name: ""}}} + + b, err := bson.Marshal(input) + if err != nil { + t.Fatal(err) + } + + output := One{} + if err := bson.Unmarshal(b, &output); err != nil { + t.Fatal(err) + } + + if len(output.Connections) != 1 { + t.Fatal(output.Connections) + } else { + for k := range output.Connections { + if k != output.Connections[k].Name { + t.Fatal(k, output.Connections) + } + } + } + + input.Connections = nil + output.Connections = nil + input.Modified = 0 + output.Modified = 0 + + if fmt.Sprint(input) != fmt.Sprint(output) { + t.Fatal(input, output) + } + }) +} diff --git a/view/json.go b/view/json.go index f076ce0..edf7c0c 100644 --- a/view/json.go +++ b/view/json.go @@ -27,6 +27,10 @@ func jsonHandler(g storage.Graph) http.Handler { foo func(g storage.Graph, w http.ResponseWriter, r *http.Request) error noauth bool }{ + { + path: "/port", + foo: port, + }, { path: "/who", foo: who, diff --git a/view/port.go b/view/port.go new file mode 100644 index 0000000..7662ba7 --- /dev/null +++ b/view/port.go @@ -0,0 +1,78 @@ +package view + +import ( + "encoding/json" + "io/ioutil" + "local/dndex/storage" + "local/dndex/storage/entity" + "net/http" + + "github.com/buger/jsonparser" +) + +func port(g storage.Graph, w http.ResponseWriter, r *http.Request) error { + switch r.Method { + case http.MethodGet: + return portGet(g, w, r) + case http.MethodPost: + return portPost(g, w, r) + default: + http.NotFound(w, r) + return nil + } +} + +func portGet(g storage.Graph, w http.ResponseWriter, r *http.Request) error { + namespace, err := getNamespace(r) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return nil + } + + ones, err := g.List(r.Context(), namespace) + if err != nil { + return err + } + + return json.NewEncoder(w).Encode(map[string]interface{}{namespace: ones}) +} + +func portPost(g storage.Graph, w http.ResponseWriter, r *http.Request) error { + namespace, err := getNamespace(r) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return nil + } + + b, err := ioutil.ReadAll(r.Body) + if err != nil { + return err + } + + var errIn error + n, err := jsonparser.ArrayEach(b, func(b []byte, _ jsonparser.ValueType, _ int, err error) { + if err != nil { + errIn = err + return + } + + o := entity.One{} + if err := json.Unmarshal(b, &o); err != nil { + errIn = err + return + } + + if err := g.Insert(r.Context(), namespace, o); err != nil { + errIn = err + return + } + }, namespace) + if err != nil { + return err + } + if errIn != nil { + return errIn + } + + return json.NewEncoder(w).Encode(map[string]int{namespace: n}) +} diff --git a/view/port_test.go b/view/port_test.go new file mode 100644 index 0000000..f560677 --- /dev/null +++ b/view/port_test.go @@ -0,0 +1,112 @@ +package view + +import ( + "bytes" + "context" + "io/ioutil" + "local/dndex/storage" + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" +) + +func TestPort(t *testing.T) { + os.Args = os.Args[:1] + f, err := ioutil.TempFile(os.TempDir(), "pattern*") + if err != nil { + t.Fatal(err) + } + f.Close() + defer os.Remove(f.Name()) + os.Setenv("DBURI", f.Name()) + os.Setenv("AUTH", "false") + + g := storage.NewGraph() + reset := func() { + if err := g.Delete(context.TODO(), "col", map[string]string{}); err != nil { + t.Fatal(err) + } + fillDB(t, g) + } + handler := jsonHandler(g) + + t.Run("port: wrong path", func(t *testing.T) { + reset() + r := httptest.NewRequest(http.MethodGet, "/port/", nil) + w := httptest.NewRecorder() + handler.ServeHTTP(w, r) + if w.Code != http.StatusNotFound { + t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) + } + }) + + t.Run("port: wrong method", func(t *testing.T) { + reset() + r := httptest.NewRequest(http.MethodPut, "/port", nil) + w := httptest.NewRecorder() + handler.ServeHTTP(w, r) + if w.Code != http.StatusNotFound { + t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) + } + }) + + t.Run("port: post: no namespace", func(t *testing.T) { + reset() + r := httptest.NewRequest(http.MethodPost, "/port", strings.NewReader(``)) + w := httptest.NewRecorder() + handler.ServeHTTP(w, r) + if w.Code != http.StatusBadRequest { + t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) + } + }) + + t.Run("port: get: no namespace", func(t *testing.T) { + reset() + r := httptest.NewRequest(http.MethodGet, "/port", nil) + w := httptest.NewRecorder() + handler.ServeHTTP(w, r) + if w.Code != http.StatusBadRequest { + t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) + } + }) + + t.Run("port: ex => im", func(t *testing.T) { + reset() + r := httptest.NewRequest(http.MethodGet, "/port?namespace=col", nil) + w := httptest.NewRecorder() + handler.ServeHTTP(w, r) + if w.Code != http.StatusOK { + t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) + } + + b, err := ioutil.ReadAll(w.Body) + if err != nil { + t.Fatal(err) + } + + if err := g.Delete(context.TODO(), "col", map[string]string{}); err != nil { + t.Fatal(err) + } + + if ones, err := g.List(context.TODO(), "col"); err != nil { + t.Fatal(err) + } else if len(ones) != 0 { + t.Fatal(len(ones)) + } + + r = httptest.NewRequest(http.MethodPost, "/port?namespace=col", bytes.NewReader(b)) + w = httptest.NewRecorder() + handler.ServeHTTP(w, r) + if w.Code != http.StatusOK { + t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) + } + + if ones, err := g.List(context.TODO(), "col"); err != nil { + t.Fatal(err) + } else if len(ones) < 10 { + t.Fatal(len(ones)) + } + }) +}