diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index d5a923c2ff..477fd7472c 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -5859,6 +5859,69 @@ paths: description: Success default: description: "" + /namespaces/{ns}/tokens/pools/{nameOrID}: + get: + description: 'TODO: Description' + operationId: getTokenPoolByNameOrID + parameters: + - description: 'TODO: Description' + in: path + name: ns + required: true + schema: + example: default + type: string + - description: 'TODO: Description' + in: path + name: nameOrID + required: true + schema: + type: string + - description: Server-side request timeout (millseconds, or set a custom suffix + like 10s) + in: header + name: Request-Timeout + schema: + default: 120s + type: string + responses: + "200": + content: + application/json: + schema: + properties: + config: + additionalProperties: {} + type: object + connector: + type: string + created: {} + id: {} + key: + type: string + message: {} + name: + type: string + namespace: + type: string + protocolId: + type: string + standard: + type: string + symbol: + type: string + tx: + properties: + id: {} + type: + type: string + type: object + type: + type: string + type: object + description: Success + default: + description: "" /namespaces/{ns}/tokens/transfers: get: description: 'TODO: Description' @@ -6005,6 +6068,67 @@ paths: description: Success default: description: "" + /namespaces/{ns}/tokens/transfers/{transferID}: + get: + description: 'TODO: Description' + operationId: getTokenTransferByID + parameters: + - description: 'TODO: Description' + in: path + name: ns + required: true + schema: + example: default + type: string + - description: 'TODO: Description' + in: path + name: transferID + required: true + schema: + type: string + - description: Server-side request timeout (millseconds, or set a custom suffix + like 10s) + in: header + name: Request-Timeout + schema: + default: 120s + type: string + responses: + "200": + content: + application/json: + schema: + properties: + amount: {} + connector: + type: string + created: {} + from: + type: string + key: + type: string + localId: {} + messageHash: {} + poolProtocolId: + type: string + protocolId: + type: string + to: + type: string + tokenIndex: + type: string + tx: + properties: + id: {} + type: + type: string + type: object + type: + type: string + type: object + description: Success + default: + description: "" /namespaces/{ns}/transactions: get: description: 'TODO: Description' diff --git a/internal/apiserver/route_get_token_pool_by_name_or_id.go b/internal/apiserver/route_get_token_pool_by_name_or_id.go new file mode 100644 index 0000000000..1fcddc7926 --- /dev/null +++ b/internal/apiserver/route_get_token_pool_by_name_or_id.go @@ -0,0 +1,46 @@ +// Copyright © 2021 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package apiserver + +import ( + "net/http" + + "github.com/hyperledger/firefly/internal/config" + "github.com/hyperledger/firefly/internal/i18n" + "github.com/hyperledger/firefly/internal/oapispec" + "github.com/hyperledger/firefly/pkg/fftypes" +) + +var getTokenPoolByNameOrID = &oapispec.Route{ + Name: "getTokenPoolByNameOrID", + Path: "namespaces/{ns}/tokens/pools/{nameOrID}", + Method: http.MethodGet, + PathParams: []*oapispec.PathParam{ + {Name: "ns", ExampleFromConf: config.NamespacesDefault, Description: i18n.MsgTBD}, + {Name: "nameOrID", Description: i18n.MsgTBD}, + }, + QueryParams: nil, + FilterFactory: nil, + Description: i18n.MsgTBD, + JSONInputValue: nil, + JSONOutputValue: func() interface{} { return &fftypes.TokenPool{} }, + JSONOutputCodes: []int{http.StatusOK}, + JSONHandler: func(r *oapispec.APIRequest) (output interface{}, err error) { + output, err = r.Or.Assets().GetTokenPoolByNameOrID(r.Ctx, r.PP["ns"], r.PP["nameOrID"]) + return output, err + }, +} diff --git a/internal/apiserver/route_get_token_pool_by_name_or_id_test.go b/internal/apiserver/route_get_token_pool_by_name_or_id_test.go new file mode 100644 index 0000000000..eea8b8a4fe --- /dev/null +++ b/internal/apiserver/route_get_token_pool_by_name_or_id_test.go @@ -0,0 +1,42 @@ +// Copyright © 2021 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package apiserver + +import ( + "net/http/httptest" + "testing" + + "github.com/hyperledger/firefly/mocks/assetmocks" + "github.com/hyperledger/firefly/pkg/fftypes" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestGetTokenPoolByNameOrID(t *testing.T) { + o, r := newTestAPIServer() + mam := &assetmocks.Manager{} + o.On("Assets").Return(mam) + req := httptest.NewRequest("GET", "/api/v1/namespaces/ns1/tokens/pools/abc", nil) + req.Header.Set("Content-Type", "application/json; charset=utf-8") + res := httptest.NewRecorder() + + mam.On("GetTokenPoolByNameOrID", mock.Anything, "ns1", "abc"). + Return(&fftypes.TokenPool{}, nil) + r.ServeHTTP(res, req) + + assert.Equal(t, 200, res.Result().StatusCode) +} diff --git a/internal/apiserver/route_get_token_transfers_by_id.go b/internal/apiserver/route_get_token_transfers_by_id.go new file mode 100644 index 0000000000..09097fbcb1 --- /dev/null +++ b/internal/apiserver/route_get_token_transfers_by_id.go @@ -0,0 +1,46 @@ +// Copyright © 2021 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package apiserver + +import ( + "net/http" + + "github.com/hyperledger/firefly/internal/config" + "github.com/hyperledger/firefly/internal/i18n" + "github.com/hyperledger/firefly/internal/oapispec" + "github.com/hyperledger/firefly/pkg/fftypes" +) + +var getTokenTransferByID = &oapispec.Route{ + Name: "getTokenTransferByID", + Path: "namespaces/{ns}/tokens/transfers/{transferID}", + Method: http.MethodGet, + PathParams: []*oapispec.PathParam{ + {Name: "ns", ExampleFromConf: config.NamespacesDefault, Description: i18n.MsgTBD}, + {Name: "transferID", Description: i18n.MsgTBD}, + }, + QueryParams: nil, + FilterFactory: nil, + Description: i18n.MsgTBD, + JSONInputValue: nil, + JSONOutputValue: func() interface{} { return &fftypes.TokenTransfer{} }, + JSONOutputCodes: []int{http.StatusOK}, + JSONHandler: func(r *oapispec.APIRequest) (output interface{}, err error) { + output, err = r.Or.Assets().GetTokenTransferByID(r.Ctx, r.PP["ns"], r.PP["transferID"]) + return output, err + }, +} diff --git a/internal/apiserver/route_get_token_transfers_by_id_test.go b/internal/apiserver/route_get_token_transfers_by_id_test.go new file mode 100644 index 0000000000..6b5044021a --- /dev/null +++ b/internal/apiserver/route_get_token_transfers_by_id_test.go @@ -0,0 +1,42 @@ +// Copyright © 2021 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package apiserver + +import ( + "net/http/httptest" + "testing" + + "github.com/hyperledger/firefly/mocks/assetmocks" + "github.com/hyperledger/firefly/pkg/fftypes" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestGetTokenTransferByID(t *testing.T) { + o, r := newTestAPIServer() + mam := &assetmocks.Manager{} + o.On("Assets").Return(mam) + req := httptest.NewRequest("GET", "/api/v1/namespaces/ns1/tokens/transfers/id1", nil) + req.Header.Set("Content-Type", "application/json; charset=utf-8") + res := httptest.NewRecorder() + + mam.On("GetTokenTransferByID", mock.Anything, "ns1", "id1"). + Return(&fftypes.TokenTransfer{}, nil) + r.ServeHTTP(res, req) + + assert.Equal(t, 200, res.Result().StatusCode) +} diff --git a/internal/apiserver/routes.go b/internal/apiserver/routes.go index 1ba4f56692..f1d981a0ab 100644 --- a/internal/apiserver/routes.go +++ b/internal/apiserver/routes.go @@ -80,11 +80,13 @@ var routes = []*oapispec.Route{ postTokenPool, getTokenPools, getTokenPoolsByType, + getTokenPoolByNameOrID, getTokenPoolByName, getTokenAccounts, getTokenAccountsByPool, getTokenTransfers, getTokenTransfersByPool, + getTokenTransferByID, postTokenMint, postTokenBurn, postTokenTransfer, diff --git a/internal/assets/manager.go b/internal/assets/manager.go index 94e86afc97..d4afaf71a5 100644 --- a/internal/assets/manager.go +++ b/internal/assets/manager.go @@ -39,10 +39,12 @@ type Manager interface { GetTokenPools(ctx context.Context, ns string, filter database.AndFilter) ([]*fftypes.TokenPool, *database.FilterResult, error) GetTokenPoolsByType(ctx context.Context, ns, connector string, filter database.AndFilter) ([]*fftypes.TokenPool, *database.FilterResult, error) GetTokenPool(ctx context.Context, ns, connector, poolName string) (*fftypes.TokenPool, error) + GetTokenPoolByNameOrID(ctx context.Context, ns string, poolNameOrID string) (*fftypes.TokenPool, error) ValidateTokenPoolTx(ctx context.Context, pool *fftypes.TokenPool, protocolTxID string) error GetTokenAccounts(ctx context.Context, ns string, filter database.AndFilter) ([]*fftypes.TokenAccount, *database.FilterResult, error) GetTokenAccountsByPool(ctx context.Context, ns, connector, poolName string, filter database.AndFilter) ([]*fftypes.TokenAccount, *database.FilterResult, error) GetTokenTransfers(ctx context.Context, ns string, filter database.AndFilter) ([]*fftypes.TokenTransfer, *database.FilterResult, error) + GetTokenTransferByID(ctx context.Context, ns, id string) (*fftypes.TokenTransfer, error) GetTokenTransfersByPool(ctx context.Context, ns, connector, poolName string, filter database.AndFilter) ([]*fftypes.TokenTransfer, *database.FilterResult, error) NewTransfer(ns, connector, poolName string, transfer *fftypes.TokenTransferInput) sysmessaging.MessageSender MintTokens(ctx context.Context, ns, connector, poolName string, transfer *fftypes.TokenTransferInput, waitConfirm bool) (*fftypes.TokenTransfer, error) diff --git a/internal/assets/token_pool.go b/internal/assets/token_pool.go index d16eb442a5..9425ffe81f 100644 --- a/internal/assets/token_pool.go +++ b/internal/assets/token_pool.go @@ -157,6 +157,30 @@ func (am *assetManager) GetTokenPool(ctx context.Context, ns, connector, poolNam return pool, nil } +func (am *assetManager) GetTokenPoolByNameOrID(ctx context.Context, ns, poolNameOrID string) (*fftypes.TokenPool, error) { + if err := fftypes.ValidateFFNameField(ctx, ns, "namespace"); err != nil { + return nil, err + } + + var pool *fftypes.TokenPool + + poolID, err := fftypes.ParseUUID(ctx, poolNameOrID) + if err != nil { + if err := fftypes.ValidateFFNameField(ctx, poolNameOrID, "name"); err != nil { + return nil, err + } + if pool, err = am.database.GetTokenPool(ctx, ns, poolNameOrID); err != nil { + return nil, err + } + } else if pool, err = am.database.GetTokenPoolByID(ctx, poolID); err != nil { + return nil, err + } + if pool == nil { + return nil, i18n.NewError(ctx, i18n.Msg404NotFound) + } + return pool, nil +} + func (am *assetManager) ValidateTokenPoolTx(ctx context.Context, pool *fftypes.TokenPool, protocolTxID string) error { // TODO: validate that the given token pool was created with the given protocolTxId return nil diff --git a/internal/assets/token_pool_test.go b/internal/assets/token_pool_test.go index 9c1a0a560d..08826a12d9 100644 --- a/internal/assets/token_pool_test.go +++ b/internal/assets/token_pool_test.go @@ -202,6 +202,75 @@ func TestGetTokenPoolBadName(t *testing.T) { assert.Regexp(t, "FF10131", err) } +func TestGetTokenPoolByID(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + u := fftypes.NewUUID() + mdi := am.database.(*databasemocks.Plugin) + mdi.On("GetTokenPoolByID", context.Background(), u).Return(&fftypes.TokenPool{}, nil) + _, err := am.GetTokenPoolByNameOrID(context.Background(), "ns1", u.String()) + assert.NoError(t, err) +} + +func TestGetTokenPoolByIDBadNamespace(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + _, err := am.GetTokenPoolByNameOrID(context.Background(), "", "") + assert.Regexp(t, "FF10131", err) +} + +func TestGetTokenPoolByIDBadID(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + u := fftypes.NewUUID() + mdi := am.database.(*databasemocks.Plugin) + mdi.On("GetTokenPoolByID", context.Background(), u).Return(nil, fmt.Errorf("pop")) + _, err := am.GetTokenPoolByNameOrID(context.Background(), "ns1", u.String()) + assert.EqualError(t, err, "pop") +} + +func TestGetTokenPoolByIDNilPool(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + u := fftypes.NewUUID() + mdi := am.database.(*databasemocks.Plugin) + mdi.On("GetTokenPoolByID", context.Background(), u).Return(nil, nil) + _, err := am.GetTokenPoolByNameOrID(context.Background(), "ns1", u.String()) + assert.Regexp(t, "FF10109", err) +} + +func TestGetTokenPoolByName(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + mdi := am.database.(*databasemocks.Plugin) + mdi.On("GetTokenPool", context.Background(), "ns1", "abc").Return(&fftypes.TokenPool{}, nil) + _, err := am.GetTokenPoolByNameOrID(context.Background(), "ns1", "abc") + assert.NoError(t, err) +} + +func TestGetTokenPoolByNameBadName(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + _, err := am.GetTokenPoolByNameOrID(context.Background(), "ns1", "") + assert.Regexp(t, "FF10131", err) +} + +func TestGetTokenPoolByNameNilPool(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + mdi := am.database.(*databasemocks.Plugin) + mdi.On("GetTokenPool", context.Background(), "ns1", "abc").Return(nil, fmt.Errorf("pop")) + _, err := am.GetTokenPoolByNameOrID(context.Background(), "ns1", "abc") + assert.EqualError(t, err, "pop") +} + func TestGetTokenPools(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() diff --git a/internal/assets/token_transfer.go b/internal/assets/token_transfer.go index 51758a70e5..332530e64b 100644 --- a/internal/assets/token_transfer.go +++ b/internal/assets/token_transfer.go @@ -37,6 +37,15 @@ func (am *assetManager) GetTokenTransfers(ctx context.Context, ns string, filter return am.database.GetTokenTransfers(ctx, filter) } +func (am *assetManager) GetTokenTransferByID(ctx context.Context, ns, id string) (*fftypes.TokenTransfer, error) { + transferID, err := fftypes.ParseUUID(ctx, id) + if err != nil { + return nil, err + } + + return am.database.GetTokenTransfer(ctx, transferID) +} + func (am *assetManager) GetTokenTransfersByPool(ctx context.Context, ns, connector, name string, filter database.AndFilter) ([]*fftypes.TokenTransfer, *database.FilterResult, error) { pool, err := am.GetTokenPool(ctx, ns, connector, name) if err != nil { diff --git a/internal/assets/token_transfer_test.go b/internal/assets/token_transfer_test.go index 543481bc52..76248154ce 100644 --- a/internal/assets/token_transfer_test.go +++ b/internal/assets/token_transfer_test.go @@ -47,6 +47,25 @@ func TestGetTokenTransfers(t *testing.T) { assert.NoError(t, err) } +func TestGetTokenTransferByID(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + u := fftypes.NewUUID() + mdi := am.database.(*databasemocks.Plugin) + mdi.On("GetTokenTransfer", context.Background(), u).Return(&fftypes.TokenTransfer{}, nil) + _, err := am.GetTokenTransferByID(context.Background(), "ns1", u.String()) + assert.NoError(t, err) +} + +func TestGetTokenTransferByIDBadID(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + + _, err := am.GetTokenTransferByID(context.Background(), "ns1", "badUUID") + assert.Regexp(t, "FF10142", err) +} + func TestGetTokenTransfersByPool(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() diff --git a/mocks/assetmocks/manager.go b/mocks/assetmocks/manager.go index a7f63b6049..059502600a 100644 --- a/mocks/assetmocks/manager.go +++ b/mocks/assetmocks/manager.go @@ -176,6 +176,29 @@ func (_m *Manager) GetTokenPool(ctx context.Context, ns string, connector string return r0, r1 } +// GetTokenPoolByNameOrID provides a mock function with given fields: ctx, ns, poolNameOrID +func (_m *Manager) GetTokenPoolByNameOrID(ctx context.Context, ns string, poolNameOrID string) (*fftypes.TokenPool, error) { + ret := _m.Called(ctx, ns, poolNameOrID) + + var r0 *fftypes.TokenPool + if rf, ok := ret.Get(0).(func(context.Context, string, string) *fftypes.TokenPool); ok { + r0 = rf(ctx, ns, poolNameOrID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*fftypes.TokenPool) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, ns, poolNameOrID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetTokenPools provides a mock function with given fields: ctx, ns, filter func (_m *Manager) GetTokenPools(ctx context.Context, ns string, filter database.AndFilter) ([]*fftypes.TokenPool, *database.FilterResult, error) { ret := _m.Called(ctx, ns, filter) @@ -240,6 +263,29 @@ func (_m *Manager) GetTokenPoolsByType(ctx context.Context, ns string, connector return r0, r1, r2 } +// GetTokenTransferByID provides a mock function with given fields: ctx, ns, id +func (_m *Manager) GetTokenTransferByID(ctx context.Context, ns string, id string) (*fftypes.TokenTransfer, error) { + ret := _m.Called(ctx, ns, id) + + var r0 *fftypes.TokenTransfer + if rf, ok := ret.Get(0).(func(context.Context, string, string) *fftypes.TokenTransfer); ok { + r0 = rf(ctx, ns, id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*fftypes.TokenTransfer) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, ns, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetTokenTransfers provides a mock function with given fields: ctx, ns, filter func (_m *Manager) GetTokenTransfers(ctx context.Context, ns string, filter database.AndFilter) ([]*fftypes.TokenTransfer, *database.FilterResult, error) { ret := _m.Called(ctx, ns, filter)