From d14586b73d17fc40c17d1b1c14dbbc011584c63e Mon Sep 17 00:00:00 2001 From: Maxim Babichev Date: Fri, 8 Sep 2023 19:53:28 +0300 Subject: [PATCH] Added unused stubs handle. Added removal of stubs by ID. --- api/openapi/api.yaml | 2 ++ internal/app/rest_server.go | 10 ++++-- internal/app/storage.go | 6 +++- internal/domain/rest/api.gen.go | 1 + pkg/sdk/api.gen.go | 1 + pkg/storage/stubs.go | 56 ++++++++++++++++++++++++++++----- stub/api_test.go | 26 +++++++++++++++ 7 files changed, 92 insertions(+), 10 deletions(-) diff --git a/api/openapi/api.yaml b/api/openapi/api.yaml index 17603dc6..66ac2779 100644 --- a/api/openapi/api.yaml +++ b/api/openapi/api.yaml @@ -192,6 +192,8 @@ components: - method - data properties: + id: + $ref: '#/components/schemas/ID' service: type: string example: Gripmock diff --git a/internal/app/rest_server.go b/internal/app/rest_server.go index 4d3134bc..8fb5daf8 100644 --- a/internal/app/rest_server.go +++ b/internal/app/rest_server.go @@ -48,6 +48,7 @@ func NewRestServer(path string) (*StubsServer, error) { // deprecated code type findStubPayload struct { + ID *uuid.UUID `json:"id,omitempty"` Service string `json:"service"` Method string `json:"method"` Data map[string]interface{} `json:"data"` @@ -128,9 +129,14 @@ func (h *StubsServer) BatchStubsDelete(w http.ResponseWriter, r *http.Request) { } } -func (h *StubsServer) ListUnusedStubs(w http.ResponseWriter, r *http.Request) { +func (h *StubsServer) ListUnusedStubs(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") - panic("ListUnusedStubs") + err := json.NewEncoder(w).Encode(h.stubs.Unused()) + if err != nil { + h.responseError(err, w) + + return + } } func (h *StubsServer) ListStubs(w http.ResponseWriter, _ *http.Request) { diff --git a/internal/app/storage.go b/internal/app/storage.go index 1d8fd0c2..61c52321 100644 --- a/internal/app/storage.go +++ b/internal/app/storage.go @@ -21,7 +21,7 @@ type closeMatch struct { } func findStub(stubStorage *storage.StubStorage, stub *findStubPayload) (*storage.Output, error) { - stubs, err := stubStorage.ItemsBy(stub.Service, stub.Method) + stubs, err := stubStorage.ItemsBy(stub.Service, stub.Method, stub.ID) if errors.Is(err, storage.ErrServiceNotFound) { //fixme //nolint:goerr113 @@ -40,6 +40,10 @@ func findStub(stubStorage *storage.StubStorage, stub *findStubPayload) (*storage return nil, fmt.Errorf("stub for Service:%s and Method:%s is empty", stub.Service, stub.Method) } + if stub.ID != nil { + return &stubs[0].Output, nil + } + var closestMatch []closeMatch for _, strange := range stubs { if expect := strange.Input.Equals; expect != nil { diff --git a/internal/domain/rest/api.gen.go b/internal/domain/rest/api.gen.go index 54de40d6..f7541e72 100644 --- a/internal/domain/rest/api.gen.go +++ b/internal/domain/rest/api.gen.go @@ -30,6 +30,7 @@ type MessageOK struct { // SearchRequest defines model for SearchRequest. type SearchRequest struct { Data interface{} `json:"data"` + Id *ID `json:"id,omitempty"` Method string `json:"method"` Service string `json:"service"` } diff --git a/pkg/sdk/api.gen.go b/pkg/sdk/api.gen.go index 90f60216..0f33ba29 100644 --- a/pkg/sdk/api.gen.go +++ b/pkg/sdk/api.gen.go @@ -34,6 +34,7 @@ type MessageOK struct { // SearchRequest defines model for SearchRequest. type SearchRequest struct { Data interface{} `json:"data"` + Id *ID `json:"id,omitempty"` Method string `json:"method"` Service string `json:"service"` } diff --git a/pkg/storage/stubs.go b/pkg/storage/stubs.go index 1c385767..a6fed21e 100644 --- a/pkg/storage/stubs.go +++ b/pkg/storage/stubs.go @@ -48,6 +48,7 @@ type storage struct { } type StubStorage struct { + used map[uuid.UUID]struct{} db *memdb.MemDB total int64 } @@ -58,7 +59,7 @@ func New() (*StubStorage, error) { return nil, err } - return &StubStorage{db: db}, nil + return &StubStorage{db: db, used: map[uuid.UUID]struct{}{}}, nil } func (r *StubStorage) Add(stubs ...*Stub) []uuid.UUID { @@ -93,8 +94,9 @@ func (r *StubStorage) Delete(args ...uuid.UUID) { var total int64 for _, arg := range args { n, _ := txn.DeleteAll(TableName, IDField, arg) - total += int64(n) + + delete(r.used, arg) } atomic.AddInt64(&r.total, -total) @@ -105,13 +107,14 @@ func (r *StubStorage) Purge() { defer txn.Commit() n, _ := txn.DeleteAll(TableName, IDField) + r.used = map[uuid.UUID]struct{}{} atomic.AddInt64(&r.total, -int64(n)) } -func (r *StubStorage) ItemsBy(service, method string) ([]storage, error) { - txn := r.db.Txn(false) - defer txn.Abort() +func (r *StubStorage) ItemsBy(service, method string, ID *uuid.UUID) ([]storage, error) { + txn := r.db.Txn(true) + defer txn.Commit() // Support for backward compatibility. Someday it will be redone... first, err := txn.First(TableName, ServiceField, service) @@ -125,7 +128,14 @@ func (r *StubStorage) ItemsBy(service, method string) ([]storage, error) { return nil, ErrMethodNotFound } - it, err := txn.Get(TableName, ServiceMethodField, service, method) + var it memdb.ResultIterator + + if ID == nil { + it, err = txn.Get(TableName, ServiceMethodField, service, method) + } else { + it, err = txn.Get(TableName, IDField, ID) + } + if err != nil { return nil, err } @@ -135,6 +145,7 @@ func (r *StubStorage) ItemsBy(service, method string) ([]storage, error) { for obj := it.Next(); obj != nil; obj = it.Next() { stub := obj.(*Stub) + r.used[stub.GetID()] = struct{}{} result = append(result, storage{ ID: stub.GetID(), Input: stub.Input, @@ -145,17 +156,48 @@ func (r *StubStorage) ItemsBy(service, method string) ([]storage, error) { return result, nil } -func (r *StubStorage) Stubs() []Stub { +func (r *StubStorage) Unused() []Stub { txn := r.db.Txn(false) defer txn.Abort() + iter, err := txn.Get(TableName, IDField) + if err != nil { + return nil + } + + it := memdb.NewFilterIterator(iter, func(raw interface{}) bool { + obj, ok := raw.(*Stub) + if !ok { + return true + } + + _, ok = r.used[obj.GetID()] + + return ok + }) + result := make([]Stub, 0, atomic.LoadInt64(&r.total)) + for obj := it.Next(); obj != nil; obj = it.Next() { + stub := obj.(*Stub) + + result = append(result, *stub) + } + + return result +} + +func (r *StubStorage) Stubs() []Stub { + txn := r.db.Txn(false) + defer txn.Abort() + it, err := txn.Get(TableName, IDField) if err != nil { return nil } + result := make([]Stub, 0, atomic.LoadInt64(&r.total)) + for obj := it.Next(); obj != nil; obj = it.Next() { stub := obj.(*Stub) diff --git a/stub/api_test.go b/stub/api_test.go index c850fa28..318dfd6b 100644 --- a/stub/api_test.go +++ b/stub/api_test.go @@ -57,6 +57,14 @@ func TestStub(t *testing.T) { handler: api.ListStubs, expect: "[{\"id\":\"43739ed8-2810-4f57-889b-4d3ff5795bce\",\"service\":\"Testing\",\"method\":\"TestMethod\",\"input\":{\"equals\":{\"Hola\":\"Mundo\"},\"contains\":null,\"matches\":null},\"output\":{\"data\":{\"Hello\":\"World\"},\"error\":\"\"}}]", }, + { + name: "unused stubs (all stubs)", + mock: func() *http.Request { + return httptest.NewRequest(http.MethodGet, "/api/stubs/unused", nil) + }, + handler: api.ListUnusedStubs, + expect: "[{\"id\":\"43739ed8-2810-4f57-889b-4d3ff5795bce\",\"service\":\"Testing\",\"method\":\"TestMethod\",\"input\":{\"equals\":{\"Hola\":\"Mundo\"},\"contains\":null,\"matches\":null},\"output\":{\"data\":{\"Hello\":\"World\"},\"error\":\"\"}}]", + }, { name: "find stub equals", mock: func() *http.Request { @@ -67,6 +75,24 @@ func TestStub(t *testing.T) { handler: api.SearchStubs, expect: "{\"data\":{\"Hello\":\"World\"},\"error\":\"\"}\n", }, + { + name: "unused stubs (zero)", + mock: func() *http.Request { + return httptest.NewRequest(http.MethodGet, "/api/stubs/unused", nil) + }, + handler: api.ListUnusedStubs, + expect: "[]", + }, + { + name: "find stub by ID", + mock: func() *http.Request { + payload := `{"id": "43739ed8-2810-4f57-889b-4d3ff5795bce", "service":"Testing","method":"TestMethod","data":{}}` + + return httptest.NewRequest(http.MethodPost, "/api/stubs/search", bytes.NewReader([]byte(payload))) + }, + handler: api.SearchStubs, + expect: "{\"data\":{\"Hello\":\"World\"},\"error\":\"\"}\n", + }, { name: "add nested stub equals", mock: func() *http.Request {