func TestRequestHandlerAuth(t *testing.T) { c := &config.Config{ NetworkPassphrase: "Test SDF Network ; September 2015", Keys: config.Keys{ // GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB SigningSeed: "SDWTLFPALQSP225BSMX7HPZ7ZEAYSUYNDLJ5QI3YGVBNRUIIELWH3XUV", }, } mockHTTPClient := new(mocks.MockHTTPClient) mockEntityManager := new(mocks.MockEntityManager) mockRepository := new(mocks.MockRepository) mockFederationResolver := new(mocks.MockFederationResolver) mockSignerVerifier := new(mocks.MockSignerVerifier) mockStellartomlResolver := new(mocks.MockStellartomlResolver) requestHandler := RequestHandler{} // Inject mocks var g inject.Graph err := g.Provide( &inject.Object{Value: &requestHandler}, &inject.Object{Value: c}, &inject.Object{Value: mockHTTPClient}, &inject.Object{Value: mockEntityManager}, &inject.Object{Value: mockRepository}, &inject.Object{Value: mockFederationResolver}, &inject.Object{Value: mockSignerVerifier}, &inject.Object{Value: mockStellartomlResolver}, ) if err != nil { panic(err) } if err := g.Populate(); err != nil { panic(err) } httpHandle := func(w http.ResponseWriter, r *http.Request) { requestHandler.HandlerAuth(web.C{}, w, r) } testServer := httptest.NewServer(http.HandlerFunc(httpHandle)) defer testServer.Close() Convey("Given auth request (no sanctions check)", t, func() { Convey("When data param is missing", func() { statusCode, response := net.GetResponse(testServer, url.Values{}) responseString := strings.TrimSpace(string(response)) assert.Equal(t, 400, statusCode) expected := test.StringToJSONMap(`{ "code": "missing_parameter", "message": "Required parameter is missing.", "data": { "name": "data" } }`) assert.Equal(t, expected, test.StringToJSONMap(responseString)) }) Convey("When data is invalid", func() { params := url.Values{ "data": {"hello world"}, "sig": {"bad sig"}, } statusCode, response := net.GetResponse(testServer, params) responseString := strings.TrimSpace(string(response)) assert.Equal(t, 400, statusCode) expected := test.StringToJSONMap(`{ "code": "invalid_parameter", "message": "Invalid parameter.", "data": { "name": "data" } }`) assert.Equal(t, expected, test.StringToJSONMap(responseString)) }) Convey("When sender's stellar.toml does not contain signing key", func() { mockStellartomlResolver.On( "GetStellarTomlByAddress", "alice*stellar.org", ).Return(stellartoml.StellarToml{}, nil).Once() params := url.Values{ "data": {"{\"sender\":\"alice*stellar.org\",\"need_info\":false,\"tx\":\"AAAAAC3/58Z9rycNLmF6voWX9VmDETFVGhFoWf66mcMuir/DAAAAZAAAAAAAAAAAAAAAAAAAAAO5TSe5k00+CKUuUtfafav6xITv43pTgO6QiPes4u/N6QAAAAEAAAAAAAAAAgAAAAFVU0QAAAAAAEbpO2riZmlZMkHuBxUBYAAas3hWyo9VL1IOdnfXAVFBAAAAADuaygAAAAAAGVL83DJFwH0sKmy6AIgJYD7GexiD0YuzSMioBCAUOJwAAAABVVNEAAAAAAAZUvzcMkXAfSwqbLoAiAlgPsZ7GIPRi7NIyKgEIBQ4nAAAAAAL68IAAAAAAgAAAAAAAAABRVVSAAAAAAALt4SwWfv1PIJvDRMenW0zu91YxZbphRFLA4O+gbAaigAAAAA=\",\"memo\":\"hello world\"}"}, "sig": {"bad sig"}, } statusCode, response := net.GetResponse(testServer, params) responseString := strings.TrimSpace(string(response)) assert.Equal(t, 400, statusCode) expected := test.StringToJSONMap(`{ "code": "invalid_parameter", "message": "Invalid parameter.", "data": { "name": "data.sender" } }`) assert.Equal(t, expected, test.StringToJSONMap(responseString)) }) Convey("When signature is invalid", func() { mockStellartomlResolver.On( "GetStellarTomlByAddress", "alice*stellar.org", ).Return(stellartoml.StellarToml{ SigningKey: "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", }, nil).Once() params := url.Values{ "data": {"{\"sender\":\"alice*stellar.org\",\"need_info\":false,\"tx\":\"AAAAAC3/58Z9rycNLmF6voWX9VmDETFVGhFoWf66mcMuir/DAAAAZAAAAAAAAAAAAAAAAAAAAAO5TSe5k00+CKUuUtfafav6xITv43pTgO6QiPes4u/N6QAAAAEAAAAAAAAAAgAAAAFVU0QAAAAAAEbpO2riZmlZMkHuBxUBYAAas3hWyo9VL1IOdnfXAVFBAAAAADuaygAAAAAAGVL83DJFwH0sKmy6AIgJYD7GexiD0YuzSMioBCAUOJwAAAABVVNEAAAAAAAZUvzcMkXAfSwqbLoAiAlgPsZ7GIPRi7NIyKgEIBQ4nAAAAAAL68IAAAAAAgAAAAAAAAABRVVSAAAAAAALt4SwWfv1PIJvDRMenW0zu91YxZbphRFLA4O+gbAaigAAAAA=\",\"memo\":\"hello world\"}"}, "sig": {"ACamNqa0dF8gf97URhFVKWSD7fmvZKc5At+8dCLM5ySR0HsHySF3G2WuwYP2nKjeqjKmu3U9Z3+u1P10w1KBCA=="}, } mockSignerVerifier.On( "Verify", "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", mock.AnythingOfType("[]uint8"), mock.AnythingOfType("[]uint8"), ).Return(errors.New("Verify error")).Once() statusCode, response := net.GetResponse(testServer, params) responseString := strings.TrimSpace(string(response)) assert.Equal(t, 400, statusCode) expected := test.StringToJSONMap(`{ "code": "invalid_parameter", "message": "Invalid parameter.", "data": { "name": "sig" } }`) assert.Equal(t, expected, test.StringToJSONMap(responseString)) }) Convey("When all params are valid", func() { params := url.Values{ "data": {"{\"sender\":\"alice*stellar.org\",\"need_info\":false,\"tx\":\"AAAAAC3/58Z9rycNLmF6voWX9VmDETFVGhFoWf66mcMuir/DAAAAZAAAAAAAAAAAAAAAAAAAAANEE2+jVbNnihFGrRb36GSelPtPwh/nfoMQwGD2HKr/igAAAAEAAAAAAAAAAgAAAAFVU0QAAAAAAEbpO2riZmlZMkHuBxUBYAAas3hWyo9VL1IOdnfXAVFBAAAAADuaygAAAAAAGVL83DJFwH0sKmy6AIgJYD7GexiD0YuzSMioBCAUOJwAAAABVVNEAAAAAAAZUvzcMkXAfSwqbLoAiAlgPsZ7GIPRi7NIyKgEIBQ4nAAAAAAL68IAAAAAAgAAAAAAAAABRVVSAAAAAAALt4SwWfv1PIJvDRMenW0zu91YxZbphRFLA4O+gbAaigAAAAA=\",\"memo\":\"{}\"}"}, "sig": {"ACamNqa0dF8gf97URhFVKWSD7fmvZKc5At+8dCLM5ySR0HsHySF3G2WuwYP2nKjeqjKmu3U9Z3+u1P10w1KBCA=="}, } mockStellartomlResolver.On( "GetStellarTomlByAddress", "alice*stellar.org", ).Return(stellartoml.StellarToml{ SigningKey: "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", }, nil).Once() mockSignerVerifier.On( "Verify", "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", mock.AnythingOfType("[]uint8"), mock.AnythingOfType("[]uint8"), ).Return(nil).Once() Convey("it returns AuthResponse", func() { authorizedTransaction := &entities.AuthorizedTransaction{ TransactionID: "29ec92f95b00dd8e8bbb0d2a2fc90db8ed5b26c396c44ac978bb13ccd8d25524", Memo: "RBNvo1WzZ4oRRq0W9+hknpT7T8If536DEMBg9hyq/4o=", TransactionXdr: "AAAAAC3/58Z9rycNLmF6voWX9VmDETFVGhFoWf66mcMuir/DAAAAZAAAAAAAAAAAAAAAAAAAAANEE2+jVbNnihFGrRb36GSelPtPwh/nfoMQwGD2HKr/igAAAAEAAAAAAAAAAgAAAAFVU0QAAAAAAEbpO2riZmlZMkHuBxUBYAAas3hWyo9VL1IOdnfXAVFBAAAAADuaygAAAAAAGVL83DJFwH0sKmy6AIgJYD7GexiD0YuzSMioBCAUOJwAAAABVVNEAAAAAAAZUvzcMkXAfSwqbLoAiAlgPsZ7GIPRi7NIyKgEIBQ4nAAAAAAL68IAAAAAAgAAAAAAAAABRVVSAAAAAAALt4SwWfv1PIJvDRMenW0zu91YxZbphRFLA4O+gbAaigAAAAA=", Data: params["data"][0], } mockEntityManager.On( "Persist", mock.AnythingOfType("*entities.AuthorizedTransaction"), ).Run(func(args mock.Arguments) { value := args.Get(0).(*entities.AuthorizedTransaction) assert.Equal(t, authorizedTransaction.TransactionID, value.TransactionID) assert.Equal(t, authorizedTransaction.Memo, value.Memo) assert.Equal(t, authorizedTransaction.TransactionXdr, value.TransactionXdr) assert.WithinDuration(t, time.Now(), value.AuthorizedAt, 2*time.Second) assert.Equal(t, authorizedTransaction.Data, value.Data) }).Return(nil).Once() statusCode, response := net.GetResponse(testServer, params) responseString := strings.TrimSpace(string(response)) assert.Equal(t, 200, statusCode) expected := test.StringToJSONMap(`{ "info_status": "ok", "tx_status": "ok" }`) assert.Equal(t, expected, test.StringToJSONMap(responseString)) }) }) }) Convey("Given auth request (sanctions check)", t, func() { c.Callbacks = config.Callbacks{ Sanctions: "http://sanctions", AskUser: "******", FetchInfo: "http://fetch_info", } memoPreimage := memo.Memo{ Transaction: memo.Transaction{ Route: "bob*acme.com", Note: "Happy birthday", SenderInfo: "senderInfoJson", Extra: "extra", }, } Convey("When all params are valid (NeedInfo = `false`)", func() { authData := compliance.AuthData{ Sender: "alice*stellar.org", NeedInfo: false, Tx: "AAAAAC3/58Z9rycNLmF6voWX9VmDETFVGhFoWf66mcMuir/DAAAAZAAAAAAAAAAAAAAAAAAAAANeCnOi6ZSpMNIMFUUVIfbBc5OA5kpzDbg+AJ6X8/WynAAAAAEAAAAAAAAAAgAAAAFVU0QAAAAAAEbpO2riZmlZMkHuBxUBYAAas3hWyo9VL1IOdnfXAVFBAAAAADuaygAAAAAAGVL83DJFwH0sKmy6AIgJYD7GexiD0YuzSMioBCAUOJwAAAABVVNEAAAAAAAZUvzcMkXAfSwqbLoAiAlgPsZ7GIPRi7NIyKgEIBQ4nAAAAAAL68IAAAAAAgAAAAAAAAABRVVSAAAAAAALt4SwWfv1PIJvDRMenW0zu91YxZbphRFLA4O+gbAaigAAAAA=", Memo: string(memoPreimage.Marshal()), } params := url.Values{ "data": {string(authData.Marshal())}, "sig": {"Q2cQVOn/A+aOxrLLeUPwHmBm3LMvlfXN8tDHo4Oi6SxWWueMTDfRkC4XvRX4emLij+Npo7/GfrZ82CnT5yB5Dg=="}, } mockStellartomlResolver.On( "GetStellarTomlByAddress", "alice*stellar.org", ).Return(stellartoml.StellarToml{ SigningKey: "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", }, nil).Once() mockSignerVerifier.On( "Verify", "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", mock.AnythingOfType("[]uint8"), mock.AnythingOfType("[]uint8"), ).Return(nil).Once() Convey("when sanctions server returns forbidden it returns tx_status `denied`", func() { mockHTTPClient.On( "PostForm", "http://sanctions", url.Values{"sender": {memoPreimage.Transaction.SenderInfo}}, ).Return( net.BuildHTTPResponse(403, "forbidden"), nil, ).Once() statusCode, response := net.GetResponse(testServer, params) responseString := strings.TrimSpace(string(response)) assert.Equal(t, 200, statusCode) expected := test.StringToJSONMap(`{ "info_status": "ok", "tx_status": "denied" }`) assert.Equal(t, expected, test.StringToJSONMap(responseString)) }) Convey("when sanctions server returns accepted it returns tx_status `pending`", func() { mockHTTPClient.On( "PostForm", "http://sanctions", url.Values{"sender": {memoPreimage.Transaction.SenderInfo}}, ).Return( net.BuildHTTPResponse(202, "pending"), nil, ).Once() statusCode, response := net.GetResponse(testServer, params) responseString := strings.TrimSpace(string(response)) assert.Equal(t, 200, statusCode) expected := test.StringToJSONMap(`{ "info_status": "ok", "tx_status": "pending", "pending": 600 }`) assert.Equal(t, expected, test.StringToJSONMap(responseString)) }) Convey("when sanctions server returns ok it returns tx_status `ok` and persists transaction", func() { mockHTTPClient.On( "PostForm", "http://sanctions", url.Values{"sender": {memoPreimage.Transaction.SenderInfo}}, ).Return( net.BuildHTTPResponse(200, "ok"), nil, ).Once() authorizedTransaction := &entities.AuthorizedTransaction{ TransactionID: "f62589932eb9fcf0bf28fe95510bf614caf3169c67a85e75475a390a79b5ecc9", Memo: "XgpzoumUqTDSDBVFFSH2wXOTgOZKcw24PgCel/P1spw=", TransactionXdr: "AAAAAC3/58Z9rycNLmF6voWX9VmDETFVGhFoWf66mcMuir/DAAAAZAAAAAAAAAAAAAAAAAAAAANeCnOi6ZSpMNIMFUUVIfbBc5OA5kpzDbg+AJ6X8/WynAAAAAEAAAAAAAAAAgAAAAFVU0QAAAAAAEbpO2riZmlZMkHuBxUBYAAas3hWyo9VL1IOdnfXAVFBAAAAADuaygAAAAAAGVL83DJFwH0sKmy6AIgJYD7GexiD0YuzSMioBCAUOJwAAAABVVNEAAAAAAAZUvzcMkXAfSwqbLoAiAlgPsZ7GIPRi7NIyKgEIBQ4nAAAAAAL68IAAAAAAgAAAAAAAAABRVVSAAAAAAALt4SwWfv1PIJvDRMenW0zu91YxZbphRFLA4O+gbAaigAAAAA=", Data: params["data"][0], } mockEntityManager.On( "Persist", mock.AnythingOfType("*entities.AuthorizedTransaction"), ).Run(func(args mock.Arguments) { value := args.Get(0).(*entities.AuthorizedTransaction) assert.Equal(t, authorizedTransaction.TransactionID, value.TransactionID) assert.Equal(t, authorizedTransaction.Memo, value.Memo) assert.Equal(t, authorizedTransaction.TransactionXdr, value.TransactionXdr) assert.WithinDuration(t, time.Now(), value.AuthorizedAt, 2*time.Second) assert.Equal(t, authorizedTransaction.Data, value.Data) }).Return(nil).Once() statusCode, response := net.GetResponse(testServer, params) responseString := strings.TrimSpace(string(response)) assert.Equal(t, 200, statusCode) expected := test.StringToJSONMap(`{ "info_status": "ok", "tx_status": "ok" }`) assert.Equal(t, expected, test.StringToJSONMap(responseString)) }) }) Convey("When all params are valid (NeedInfo = `true`)", func() { authData := compliance.AuthData{ Sender: "alice*stellar.org", NeedInfo: true, Tx: "AAAAAC3/58Z9rycNLmF6voWX9VmDETFVGhFoWf66mcMuir/DAAAAZAAAAAAAAAAAAAAAAAAAAANeCnOi6ZSpMNIMFUUVIfbBc5OA5kpzDbg+AJ6X8/WynAAAAAEAAAAAAAAAAgAAAAFVU0QAAAAAAEbpO2riZmlZMkHuBxUBYAAas3hWyo9VL1IOdnfXAVFBAAAAADuaygAAAAAAGVL83DJFwH0sKmy6AIgJYD7GexiD0YuzSMioBCAUOJwAAAABVVNEAAAAAAAZUvzcMkXAfSwqbLoAiAlgPsZ7GIPRi7NIyKgEIBQ4nAAAAAAL68IAAAAAAgAAAAAAAAABRVVSAAAAAAALt4SwWfv1PIJvDRMenW0zu91YxZbphRFLA4O+gbAaigAAAAA=", Memo: string(memoPreimage.Marshal()), } params := url.Values{ "data": {string(authData.Marshal())}, "sig": {"Q2cQVOn/A+aOxrLLeUPwHmBm3LMvlfXN8tDHo4Oi6SxWWueMTDfRkC4XvRX4emLij+Npo7/GfrZ82CnT5yB5Dg=="}, } mockStellartomlResolver.On( "GetStellarTomlByAddress", "alice*stellar.org", ).Return(stellartoml.StellarToml{ SigningKey: "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", }, nil).Once() mockSignerVerifier.On( "Verify", "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", mock.AnythingOfType("[]uint8"), mock.AnythingOfType("[]uint8"), ).Return(nil).Once() // Make sanctions checks successful (tested in the previous test case) mockHTTPClient.On( "PostForm", "http://sanctions", url.Values{ "sender": {memoPreimage.Transaction.SenderInfo}, }, ).Return( net.BuildHTTPResponse(200, "ok"), nil, ).Once() Convey("when ask_user server returns forbidden it returns info_status `denied`", func() { mockHTTPClient.On( "PostForm", "http://ask_user", url.Values{ "sender": {memoPreimage.Transaction.SenderInfo}, "note": {memoPreimage.Transaction.Note}, "amount": {"20.0000000"}, "asset_code": {"USD"}, "asset_issuer": {"GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE"}, }, ).Return( net.BuildHTTPResponse(403, "forbidden"), nil, ).Once() statusCode, response := net.GetResponse(testServer, params) responseString := strings.TrimSpace(string(response)) assert.Equal(t, 200, statusCode) expected := test.StringToJSONMap(`{ "info_status": "denied", "tx_status": "ok" }`) assert.Equal(t, expected, test.StringToJSONMap(responseString)) }) Convey("when ask_user server returns pending it returns info_status `pending`", func() { mockHTTPClient.On( "PostForm", "http://ask_user", url.Values{ "sender": {memoPreimage.Transaction.SenderInfo}, "note": {memoPreimage.Transaction.Note}, "amount": {"20.0000000"}, "asset_code": {"USD"}, "asset_issuer": {"GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE"}, }, ).Return( net.BuildHTTPResponse(202, "{\"pending\": 300}"), nil, ).Once() statusCode, response := net.GetResponse(testServer, params) responseString := strings.TrimSpace(string(response)) assert.Equal(t, 200, statusCode) expected := test.StringToJSONMap(`{ "info_status": "pending", "tx_status": "ok", "pending": 300 }`) assert.Equal(t, expected, test.StringToJSONMap(responseString)) }) Convey("when ask_user server returns pending but invalid response body it returns info_status `pending` (600 seconds)", func() { mockHTTPClient.On( "PostForm", "http://ask_user", url.Values{ "sender": {memoPreimage.Transaction.SenderInfo}, "note": {memoPreimage.Transaction.Note}, "amount": {"20.0000000"}, "asset_code": {"USD"}, "asset_issuer": {"GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE"}, }, ).Return( net.BuildHTTPResponse(202, "pending"), nil, ).Once() statusCode, response := net.GetResponse(testServer, params) responseString := strings.TrimSpace(string(response)) assert.Equal(t, 200, statusCode) expected := test.StringToJSONMap(`{ "info_status": "pending", "tx_status": "ok", "pending": 600 }`) assert.Equal(t, expected, test.StringToJSONMap(responseString)) }) Convey("when ask_user server returns ok it returns info_status `ok` and DestInfo and persists transaction", func() { mockHTTPClient.On( "PostForm", "http://ask_user", url.Values{ "sender": {memoPreimage.Transaction.SenderInfo}, "note": {memoPreimage.Transaction.Note}, "amount": {"20.0000000"}, "asset_code": {"USD"}, "asset_issuer": {"GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE"}, }, ).Return( net.BuildHTTPResponse(200, "ok"), nil, ).Once() mockHTTPClient.On( "PostForm", "http://fetch_info", url.Values{"address": {"bob*acme.com"}}, ).Return( net.BuildHTTPResponse(200, "user data"), nil, ).Once() authorizedTransaction := &entities.AuthorizedTransaction{ TransactionID: "f62589932eb9fcf0bf28fe95510bf614caf3169c67a85e75475a390a79b5ecc9", Memo: "XgpzoumUqTDSDBVFFSH2wXOTgOZKcw24PgCel/P1spw=", TransactionXdr: "AAAAAC3/58Z9rycNLmF6voWX9VmDETFVGhFoWf66mcMuir/DAAAAZAAAAAAAAAAAAAAAAAAAAANeCnOi6ZSpMNIMFUUVIfbBc5OA5kpzDbg+AJ6X8/WynAAAAAEAAAAAAAAAAgAAAAFVU0QAAAAAAEbpO2riZmlZMkHuBxUBYAAas3hWyo9VL1IOdnfXAVFBAAAAADuaygAAAAAAGVL83DJFwH0sKmy6AIgJYD7GexiD0YuzSMioBCAUOJwAAAABVVNEAAAAAAAZUvzcMkXAfSwqbLoAiAlgPsZ7GIPRi7NIyKgEIBQ4nAAAAAAL68IAAAAAAgAAAAAAAAABRVVSAAAAAAALt4SwWfv1PIJvDRMenW0zu91YxZbphRFLA4O+gbAaigAAAAA=", Data: params["data"][0], } mockEntityManager.On( "Persist", mock.AnythingOfType("*entities.AuthorizedTransaction"), ).Run(func(args mock.Arguments) { value := args.Get(0).(*entities.AuthorizedTransaction) assert.Equal(t, authorizedTransaction.TransactionID, value.TransactionID) assert.Equal(t, authorizedTransaction.Memo, value.Memo) assert.Equal(t, authorizedTransaction.TransactionXdr, value.TransactionXdr) assert.WithinDuration(t, time.Now(), value.AuthorizedAt, 2*time.Second) assert.Equal(t, authorizedTransaction.Data, value.Data) }).Return(nil).Once() statusCode, response := net.GetResponse(testServer, params) responseString := strings.TrimSpace(string(response)) assert.Equal(t, 200, statusCode) expected := test.StringToJSONMap(`{ "info_status": "ok", "tx_status": "ok", "dest_info": "user data" }`) assert.Equal(t, expected, test.StringToJSONMap(responseString)) }) Convey("When no callbacks.ask_user server", func() { c.Callbacks.AskUser = "" Convey("when FI allowed it returns info_status = `ok` and DestInfo and persists transaction", func() { mockRepository.On( "GetAllowedFiByDomain", "stellar.org", // sender = `alice*stellar.org` ).Return( &entities.AllowedFi{}, // It just returns existing record nil, ).Once() mockHTTPClient.On( "PostForm", "http://fetch_info", url.Values{"address": {"bob*acme.com"}}, ).Return( net.BuildHTTPResponse(200, "user data"), nil, ).Once() authorizedTransaction := &entities.AuthorizedTransaction{ TransactionID: "f62589932eb9fcf0bf28fe95510bf614caf3169c67a85e75475a390a79b5ecc9", Memo: "XgpzoumUqTDSDBVFFSH2wXOTgOZKcw24PgCel/P1spw=", TransactionXdr: "AAAAAC3/58Z9rycNLmF6voWX9VmDETFVGhFoWf66mcMuir/DAAAAZAAAAAAAAAAAAAAAAAAAAANeCnOi6ZSpMNIMFUUVIfbBc5OA5kpzDbg+AJ6X8/WynAAAAAEAAAAAAAAAAgAAAAFVU0QAAAAAAEbpO2riZmlZMkHuBxUBYAAas3hWyo9VL1IOdnfXAVFBAAAAADuaygAAAAAAGVL83DJFwH0sKmy6AIgJYD7GexiD0YuzSMioBCAUOJwAAAABVVNEAAAAAAAZUvzcMkXAfSwqbLoAiAlgPsZ7GIPRi7NIyKgEIBQ4nAAAAAAL68IAAAAAAgAAAAAAAAABRVVSAAAAAAALt4SwWfv1PIJvDRMenW0zu91YxZbphRFLA4O+gbAaigAAAAA=", Data: params["data"][0], } mockEntityManager.On( "Persist", mock.AnythingOfType("*entities.AuthorizedTransaction"), ).Run(func(args mock.Arguments) { value := args.Get(0).(*entities.AuthorizedTransaction) assert.Equal(t, authorizedTransaction.TransactionID, value.TransactionID) assert.Equal(t, authorizedTransaction.Memo, value.Memo) assert.Equal(t, authorizedTransaction.TransactionXdr, value.TransactionXdr) assert.WithinDuration(t, time.Now(), value.AuthorizedAt, 2*time.Second) assert.Equal(t, authorizedTransaction.Data, value.Data) }).Return(nil).Once() statusCode, response := net.GetResponse(testServer, params) responseString := strings.TrimSpace(string(response)) assert.Equal(t, 200, statusCode) expected := test.StringToJSONMap(`{ "info_status": "ok", "tx_status": "ok", "dest_info": "user data" }`) assert.Equal(t, expected, test.StringToJSONMap(responseString)) }) Convey("when FI not allowed but User is allowed it returns info_status = `ok` and DestInfo and persists transaction", func() { mockRepository.On( "GetAllowedFiByDomain", "stellar.org", // sender = `alice*stellar.org` ).Return( nil, nil, ).Once() mockRepository.On( "GetAllowedUserByDomainAndUserID", "stellar.org", // sender = `alice*stellar.org` "alice", ).Return( &entities.AllowedUser{}, nil, ).Once() mockHTTPClient.On( "PostForm", "http://fetch_info", url.Values{"address": {"bob*acme.com"}}, ).Return( net.BuildHTTPResponse(200, "user data"), nil, ).Once() authorizedTransaction := &entities.AuthorizedTransaction{ TransactionID: "f62589932eb9fcf0bf28fe95510bf614caf3169c67a85e75475a390a79b5ecc9", Memo: "XgpzoumUqTDSDBVFFSH2wXOTgOZKcw24PgCel/P1spw=", TransactionXdr: "AAAAAC3/58Z9rycNLmF6voWX9VmDETFVGhFoWf66mcMuir/DAAAAZAAAAAAAAAAAAAAAAAAAAANeCnOi6ZSpMNIMFUUVIfbBc5OA5kpzDbg+AJ6X8/WynAAAAAEAAAAAAAAAAgAAAAFVU0QAAAAAAEbpO2riZmlZMkHuBxUBYAAas3hWyo9VL1IOdnfXAVFBAAAAADuaygAAAAAAGVL83DJFwH0sKmy6AIgJYD7GexiD0YuzSMioBCAUOJwAAAABVVNEAAAAAAAZUvzcMkXAfSwqbLoAiAlgPsZ7GIPRi7NIyKgEIBQ4nAAAAAAL68IAAAAAAgAAAAAAAAABRVVSAAAAAAALt4SwWfv1PIJvDRMenW0zu91YxZbphRFLA4O+gbAaigAAAAA=", Data: params["data"][0], } mockEntityManager.On( "Persist", mock.AnythingOfType("*entities.AuthorizedTransaction"), ).Run(func(args mock.Arguments) { value := args.Get(0).(*entities.AuthorizedTransaction) assert.Equal(t, authorizedTransaction.TransactionID, value.TransactionID) assert.Equal(t, authorizedTransaction.Memo, value.Memo) assert.Equal(t, authorizedTransaction.TransactionXdr, value.TransactionXdr) assert.WithinDuration(t, time.Now(), value.AuthorizedAt, 2*time.Second) assert.Equal(t, authorizedTransaction.Data, value.Data) }).Return(nil).Once() statusCode, response := net.GetResponse(testServer, params) responseString := strings.TrimSpace(string(response)) assert.Equal(t, 200, statusCode) expected := test.StringToJSONMap(`{ "info_status": "ok", "tx_status": "ok", "dest_info": "user data" }`) assert.Equal(t, expected, test.StringToJSONMap(responseString)) }) Convey("when neither FI nor User is allowed it returns info_status = `denied`", func() { mockRepository.On( "GetAllowedFiByDomain", "stellar.org", // sender = `alice*stellar.org` ).Return( nil, nil, ).Once() mockRepository.On( "GetAllowedUserByDomainAndUserID", "stellar.org", // sender = `alice*stellar.org` "alice", ).Return( nil, nil, ).Once() statusCode, response := net.GetResponse(testServer, params) responseString := strings.TrimSpace(string(response)) assert.Equal(t, 200, statusCode) expected := test.StringToJSONMap(`{ "info_status": "denied", "tx_status": "ok" }`) assert.Equal(t, expected, test.StringToJSONMap(responseString)) }) }) }) }) }
func TestPaymentListener(t *testing.T) { mockEntityManager := new(mocks.MockEntityManager) mockHorizon := new(mocks.MockHorizon) mockRepository := new(mocks.MockRepository) var receiveHookStatusCode int receiveHookServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "POST", r.Method) assert.Equal(t, "1", r.FormValue("id")) assert.Equal(t, "GBIHSMPXC2KJ3NJVHEYTG3KCHYEUQRT45X6AWYWXMAXZOAX4F5LFZYYQ", r.FormValue("from")) assert.Equal(t, "200", r.FormValue("amount")) assert.Equal(t, "USD", r.FormValue("asset_code")) assert.Equal(t, "text", r.FormValue("memo_type")) assert.Equal(t, "testing", r.FormValue("memo")) w.WriteHeader(receiveHookStatusCode) fmt.Fprintln(w, "Response") })) defer receiveHookServer.Close() IssuingSeed := "SC34WILLHVADXMP6ACPMIRA6TRAWJMVCLPFNW7S6MUMXJVLAZUC4EWHP" ReceivingAccountId := "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB" config := &config.Config{ Assets: []string{"USD", "EUR"}, Accounts: &config.Accounts{ // GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR IssuingSeed: &IssuingSeed, ReceivingAccountId: &ReceivingAccountId, }, Hooks: &config.Hooks{ Receive: &receiveHookServer.URL, }, } paymentListener, _ := NewPaymentListener( config, mockEntityManager, mockHorizon, mockRepository, mocks.Now, ) Convey("PaymentListener", t, func() { operation := horizon.PaymentResponse{ Id: "1", From: "GBIHSMPXC2KJ3NJVHEYTG3KCHYEUQRT45X6AWYWXMAXZOAX4F5LFZYYQ", PagingToken: "2", Amount: "200", } mocks.PredefinedTime = time.Now() dbPayment := db.ReceivedPayment{ OperationId: operation.Id, ProcessedAt: mocks.PredefinedTime, PagingToken: operation.PagingToken, } Convey("When operation is not a payment", func() { operation.Type = "create_account" dbPayment.Status = "Not a payment operation" mockEntityManager.On("Persist", &dbPayment).Return(nil).Once() Convey("it should save the status", func() { err := paymentListener.onPayment(operation) assert.Nil(t, err) mockEntityManager.AssertExpectations(t) }) }) Convey("When payment is sent not received", func() { operation.Type = "payment" operation.To = "GDNXBMIJLLLXZYKZBHXJ45WQ4AJQBRVT776YKGQTDBHTSPMNAFO3OZOS" dbPayment.Status = "Operation sent not received" mockEntityManager.On("Persist", &dbPayment).Return(nil).Once() Convey("it should save the status", func() { err := paymentListener.onPayment(operation) assert.Nil(t, err) mockEntityManager.AssertExpectations(t) }) }) Convey("When asset is not allowed (issuer)", func() { operation.Type = "payment" operation.To = "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB" operation.AssetCode = "USD" operation.AssetIssuer = "GC4WWLMUGZJMRVJM7JUVVZBY3LJ5HL4RKIPADEGKEMLAAJEDRONUGYG7" dbPayment.Status = "Asset not allowed" mockEntityManager.On("Persist", &dbPayment).Return(nil).Once() Convey("it should save the status", func() { err := paymentListener.onPayment(operation) assert.Nil(t, err) mockEntityManager.AssertExpectations(t) }) }) Convey("When asset is not allowed (code)", func() { operation.Type = "payment" operation.To = "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB" operation.AssetCode = "GBP" operation.AssetIssuer = "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR" dbPayment.Status = "Asset not allowed" mockEntityManager.On("Persist", &dbPayment).Return(nil).Once() Convey("it should save the status", func() { err := paymentListener.onPayment(operation) assert.Nil(t, err) mockEntityManager.AssertExpectations(t) }) }) Convey("When transaction does not have memo", func() { operation.Type = "payment" operation.To = "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB" operation.AssetCode = "USD" operation.AssetIssuer = "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR" dbPayment.Status = "Transaction does not have memo" mockHorizon.On("LoadMemo", &operation).Return(nil).Once() mockEntityManager.On("Persist", &dbPayment).Return(nil).Once() Convey("it should save the status", func() { err := paymentListener.onPayment(operation) assert.Nil(t, err) mockHorizon.AssertExpectations(t) mockEntityManager.AssertExpectations(t) }) }) Convey("When unable to load transaction memo", func() { operation.Type = "payment" operation.To = "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB" operation.AssetCode = "USD" operation.AssetIssuer = "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR" mockHorizon.On("LoadMemo", &operation).Return(errors.New("Connection error")).Once() Convey("it should return error", func() { err := paymentListener.onPayment(operation) assert.Error(t, err) mockHorizon.AssertExpectations(t) mockEntityManager.AssertNotCalled(t, "Persist") }) }) Convey("When receive hook returns error", func() { operation.Type = "payment" operation.To = "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB" operation.AssetCode = "USD" operation.AssetIssuer = "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR" operation.Memo.Type = "text" operation.Memo.Value = "testing" mockHorizon.On("LoadMemo", &operation).Return(nil).Once() receiveHookStatusCode = 503 Convey("it should save the status", func() { err := paymentListener.onPayment(operation) assert.Error(t, err) mockHorizon.AssertExpectations(t) mockEntityManager.AssertNotCalled(t, "Persist") }) }) Convey("When receive hook returns success", func() { operation.Type = "payment" operation.To = "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB" operation.AssetCode = "USD" operation.AssetIssuer = "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR" operation.Memo.Type = "text" operation.Memo.Value = "testing" dbPayment.Status = "Success" mockHorizon.On("LoadMemo", &operation).Return(nil).Once() mockEntityManager.On("Persist", &dbPayment).Return(nil).Once() receiveHookStatusCode = 200 Convey("it should save the status", func() { err := paymentListener.onPayment(operation) assert.Nil(t, err) mockHorizon.AssertExpectations(t) mockEntityManager.AssertExpectations(t) }) }) }) }
func TestPaymentListener(t *testing.T) { mockEntityManager := new(mocks.MockEntityManager) mockHorizon := new(mocks.MockHorizon) mockRepository := new(mocks.MockRepository) mockHTTPClient := new(mocks.MockHTTPClient) config := &config.Config{ Assets: []config.Asset{ {Code: "USD", Issuer: "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR"}, {Code: "EUR", Issuer: "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR"}, }, Accounts: config.Accounts{ IssuingAccountID: "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB", ReceivingAccountID: "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB", }, Callbacks: config.Callbacks{ Receive: "http://receive_callback", }, } paymentListener, _ := NewPaymentListener( config, mockEntityManager, mockHorizon, mockRepository, mocks.Now, ) paymentListener.client = mockHTTPClient Convey("PaymentListener", t, func() { operation := horizon.PaymentResponse{ ID: "1", From: "GBIHSMPXC2KJ3NJVHEYTG3KCHYEUQRT45X6AWYWXMAXZOAX4F5LFZYYQ", PagingToken: "2", Amount: "200", } mocks.PredefinedTime = time.Now() dbPayment := entities.ReceivedPayment{ OperationID: operation.ID, ProcessedAt: mocks.PredefinedTime, PagingToken: operation.PagingToken, } Convey("When operation exists", func() { operation.Type = "payment" mockRepository.On("GetReceivedPaymentByID", int64(1)).Return(&entities.ReceivedPayment{}, nil).Once() Convey("it should save the status", func() { err := paymentListener.onPayment(operation) assert.Nil(t, err) mockEntityManager.AssertExpectations(t) }) }) Convey("When operation is not a payment", func() { operation.Type = "create_account" dbPayment.Status = "Not a payment operation" mockEntityManager.On("Persist", &dbPayment).Return(nil).Once() mockRepository.On("GetReceivedPaymentByID", int64(1)).Return(nil, nil).Once() Convey("it should save the status", func() { err := paymentListener.onPayment(operation) assert.Nil(t, err) mockEntityManager.AssertExpectations(t) }) }) Convey("When payment is sent not received", func() { operation.Type = "payment" operation.To = "GDNXBMIJLLLXZYKZBHXJ45WQ4AJQBRVT776YKGQTDBHTSPMNAFO3OZOS" dbPayment.Status = "Operation sent not received" mockEntityManager.On("Persist", &dbPayment).Return(nil).Once() mockRepository.On("GetReceivedPaymentByID", int64(1)).Return(nil, nil).Once() Convey("it should save the status", func() { err := paymentListener.onPayment(operation) assert.Nil(t, err) mockEntityManager.AssertExpectations(t) }) }) Convey("When asset is not allowed (issuer)", func() { operation.Type = "payment" operation.To = "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB" operation.AssetCode = "USD" operation.AssetIssuer = "GC4WWLMUGZJMRVJM7JUVVZBY3LJ5HL4RKIPADEGKEMLAAJEDRONUGYG7" dbPayment.Status = "Asset not allowed" mockEntityManager.On("Persist", &dbPayment).Return(nil).Once() mockRepository.On("GetReceivedPaymentByID", int64(1)).Return(nil, nil).Once() Convey("it should save the status", func() { err := paymentListener.onPayment(operation) assert.Nil(t, err) mockEntityManager.AssertExpectations(t) }) }) Convey("When asset is not allowed (code)", func() { operation.Type = "payment" operation.To = "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB" operation.AssetCode = "GBP" operation.AssetIssuer = "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR" dbPayment.Status = "Asset not allowed" mockEntityManager.On("Persist", &dbPayment).Return(nil).Once() mockRepository.On("GetReceivedPaymentByID", int64(1)).Return(nil, nil).Once() Convey("it should save the status", func() { err := paymentListener.onPayment(operation) assert.Nil(t, err) mockEntityManager.AssertExpectations(t) }) }) Convey("When unable to load transaction memo", func() { operation.Type = "payment" operation.To = "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB" operation.AssetCode = "USD" operation.AssetIssuer = "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR" mockRepository.On("GetReceivedPaymentByID", int64(1)).Return(nil, nil).Once() mockHorizon.On("LoadMemo", &operation).Return(errors.New("Connection error")).Once() Convey("it should return error", func() { err := paymentListener.onPayment(operation) assert.Error(t, err) mockHorizon.AssertExpectations(t) mockEntityManager.AssertNotCalled(t, "Persist") }) }) Convey("When receive callback returns error", func() { operation.Type = "payment" operation.To = "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB" operation.AssetCode = "USD" operation.AssetIssuer = "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR" operation.Memo.Type = "text" operation.Memo.Value = "testing" mockRepository.On("GetReceivedPaymentByID", int64(1)).Return(nil, nil).Once() mockHorizon.On("LoadMemo", &operation).Return(nil).Once() mockHTTPClient.On( "PostForm", "http://receive_callback", url.Values{ "id": {"1"}, "from": {"GBIHSMPXC2KJ3NJVHEYTG3KCHYEUQRT45X6AWYWXMAXZOAX4F5LFZYYQ"}, "amount": {"200"}, "asset_code": {"USD"}, "memo_type": {"text"}, "memo": {"testing"}, "data": {""}, }, ).Return( net.BuildHTTPResponse(503, "ok"), nil, ).Once() Convey("it should save the status", func() { err := paymentListener.onPayment(operation) assert.Error(t, err) mockHorizon.AssertExpectations(t) mockEntityManager.AssertNotCalled(t, "Persist") }) }) Convey("When receive callback returns success", func() { operation.Type = "payment" operation.To = "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB" operation.AssetCode = "USD" operation.AssetIssuer = "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR" operation.Memo.Type = "text" operation.Memo.Value = "testing" dbPayment.Status = "Success" mockRepository.On("GetReceivedPaymentByID", int64(1)).Return(nil, nil).Once() mockHorizon.On("LoadMemo", &operation).Return(nil).Once() mockEntityManager.On("Persist", &dbPayment).Return(nil).Once() mockHTTPClient.On( "PostForm", "http://receive_callback", url.Values{ "id": {"1"}, "from": {"GBIHSMPXC2KJ3NJVHEYTG3KCHYEUQRT45X6AWYWXMAXZOAX4F5LFZYYQ"}, "amount": {"200"}, "asset_code": {"USD"}, "memo_type": {"text"}, "memo": {"testing"}, "data": {""}, }, ).Return( net.BuildHTTPResponse(200, "ok"), nil, ).Once() Convey("it should save the status", func() { err := paymentListener.onPayment(operation) assert.Nil(t, err) mockHorizon.AssertExpectations(t) mockEntityManager.AssertExpectations(t) }) }) Convey("When receive callback returns success (no memo)", func() { operation.Type = "payment" operation.To = "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB" operation.AssetCode = "USD" operation.AssetIssuer = "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR" dbPayment.Status = "Success" mockRepository.On("GetReceivedPaymentByID", int64(1)).Return(nil, nil).Once() mockHorizon.On("LoadMemo", &operation).Return(nil).Once() mockEntityManager.On("Persist", &dbPayment).Return(nil).Once() mockHTTPClient.On( "PostForm", "http://receive_callback", url.Values{ "id": {"1"}, "from": {"GBIHSMPXC2KJ3NJVHEYTG3KCHYEUQRT45X6AWYWXMAXZOAX4F5LFZYYQ"}, "amount": {"200"}, "asset_code": {"USD"}, "memo_type": {""}, "memo": {""}, "data": {""}, }, ).Return( net.BuildHTTPResponse(200, "ok"), nil, ).Once() Convey("it should save the status", func() { err := paymentListener.onPayment(operation) assert.Nil(t, err) mockHorizon.AssertExpectations(t) mockEntityManager.AssertExpectations(t) }) }) Convey("When receive callback returns success and compliance server is connected", func() { paymentListener.config.Compliance = "http://compliance" operation.Type = "payment" operation.To = "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB" operation.AssetCode = "USD" operation.AssetIssuer = "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR" operation.Memo.Type = "hash" operation.Memo.Value = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9" dbPayment.Status = "Success" mockRepository.On("GetReceivedPaymentByID", int64(1)).Return(nil, nil).Once() mockHorizon.On("LoadMemo", &operation).Return(nil).Once() mockEntityManager.On("Persist", &dbPayment).Return(nil).Once() mockHTTPClient.On( "PostForm", "http://compliance/receive", url.Values{"memo": {"b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"}}, ).Return( net.BuildHTTPResponse(200, "{\"data\": \"hello world\"}"), nil, ).Once() mockHTTPClient.On( "PostForm", "http://receive_callback", url.Values{ "id": {"1"}, "from": {"GBIHSMPXC2KJ3NJVHEYTG3KCHYEUQRT45X6AWYWXMAXZOAX4F5LFZYYQ"}, "amount": {"200"}, "asset_code": {"USD"}, "memo_type": {"hash"}, "memo": {"b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"}, "data": {"hello world"}, }, ).Return( net.BuildHTTPResponse(200, "ok"), nil, ).Once() Convey("it should save the status", func() { err := paymentListener.onPayment(operation) assert.Nil(t, err) mockHorizon.AssertExpectations(t) mockEntityManager.AssertExpectations(t) }) }) }) }
func TestTransactionSubmitter(t *testing.T) { mockHorizon := new(mocks.MockHorizon) mockEntityManager := new(mocks.MockEntityManager) mocks.PredefinedTime = time.Now() Convey("TransactionSubmitter", t, func() { seed := "SDZT3EJZ7FZRYNTLOZ7VH6G5UYBFO2IO3Q5PGONMILPCZU3AL7QNZHTE" accountID := "GCLOMB72ODBFUGK4E2BK7VMR3RNZ5WSTMEOGNA2YUVHFR3WMH2XBAB6H" Convey("LoadAccount", func() { transactionSubmitter := NewTransactionSubmitter( mockHorizon, mockEntityManager, "Test SDF Network ; September 2015", mocks.Now, ) Convey("When seed is invalid", func() { _, err := transactionSubmitter.LoadAccount("invalidSeed") assert.NotNil(t, err) }) Convey("When there is a problem loading an account", func() { mockHorizon.On( "LoadAccount", accountID, ).Return( horizon.AccountResponse{}, errors.New("Account not found"), ).Once() _, err := transactionSubmitter.LoadAccount(seed) assert.NotNil(t, err) mockHorizon.AssertExpectations(t) }) Convey("Successfully loads an account", func() { mockHorizon.On( "LoadAccount", accountID, ).Return( horizon.AccountResponse{ AccountID: accountID, SequenceNumber: "10372672437354496", }, nil, ).Once() account, err := transactionSubmitter.LoadAccount(seed) assert.Nil(t, err) assert.Equal(t, account.Keypair.Address(), accountID) assert.Equal(t, account.Seed, seed) assert.Equal(t, account.SequenceNumber, uint64(10372672437354496)) mockHorizon.AssertExpectations(t) }) }) Convey("SubmitTransaction", func() { Convey("Submits transaction without a memo", func() { operation := b.Payment( b.Destination{"GB3W7VQ2A2IOQIS4LUFUMRC2DWXONUDH24ROLE6RS4NGUNHVSXKCABOM"}, b.NativeAmount{"100"}, ) Convey("Error response from horizon", func() { transactionSubmitter := NewTransactionSubmitter( mockHorizon, mockEntityManager, "Test SDF Network ; September 2015", mocks.Now, ) mockHorizon.On( "LoadAccount", accountID, ).Return( horizon.AccountResponse{ AccountID: accountID, SequenceNumber: "10372672437354496", }, nil, ).Once() err := transactionSubmitter.InitAccount(seed) assert.Nil(t, err) txB64 := "AAAAAJbmB/pwwloZXCaCr9WR3Fue2lNhHGaDWKVOWO7MPq4QAAAAZAAk2eQAAAABAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAAd2/WGgaQ6CJcXQtGRFodrubQZ9ci5ZPRlxpqNPWV1CAAAAAAAAAAADuaygAAAAAAAAAAAcw+rhAAAABAyFjIMIZOtstCWtZlVBDj1AhTmsk5v1i2GGY4by2b5mgZoXXGgFTB8sfbQav0LzFKCcxY8h+9xPMT2e9xznAfDw==" // Persist sending transaction mockEntityManager.On( "Persist", mock.AnythingOfType("*entities.SentTransaction"), ).Return(nil).Once().Run(func(args mock.Arguments) { transaction := args.Get(0).(*entities.SentTransaction) assert.Equal(t, "4f885999be6ea7891052a53e496bcfb5c5a1a5bfb31923f649b028fdc74dd050", transaction.TransactionID) assert.Equal(t, "sending", string(transaction.Status)) assert.Equal(t, "GCLOMB72ODBFUGK4E2BK7VMR3RNZ5WSTMEOGNA2YUVHFR3WMH2XBAB6H", transaction.Source) assert.Equal(t, mocks.PredefinedTime, transaction.SubmittedAt) assert.Equal(t, txB64, transaction.EnvelopeXdr) }) // Persist failure mockEntityManager.On( "Persist", mock.AnythingOfType("*entities.SentTransaction"), ).Return(nil).Once().Run(func(args mock.Arguments) { transaction := args.Get(0).(*entities.SentTransaction) assert.Equal(t, "4f885999be6ea7891052a53e496bcfb5c5a1a5bfb31923f649b028fdc74dd050", transaction.TransactionID) assert.Equal(t, "failure", string(transaction.Status)) assert.Equal(t, "GCLOMB72ODBFUGK4E2BK7VMR3RNZ5WSTMEOGNA2YUVHFR3WMH2XBAB6H", transaction.Source) assert.Equal(t, mocks.PredefinedTime, transaction.SubmittedAt) assert.Equal(t, txB64, transaction.EnvelopeXdr) }) mockHorizon.On("SubmitTransaction", txB64).Return( horizon.SubmitTransactionResponse{ Ledger: nil, Extras: &horizon.SubmitTransactionResponseExtras{ ResultXdr: "AAAAAAAAAGT/////AAAAAQAAAAAAAAAB////+wAAAAA=", // no_destination }, }, nil, ).Once() _, err = transactionSubmitter.SubmitTransaction(seed, operation, nil) assert.Nil(t, err) mockHorizon.AssertExpectations(t) }) Convey("Bad Sequence response from horizon", func() { transactionSubmitter := NewTransactionSubmitter( mockHorizon, mockEntityManager, "Test SDF Network ; September 2015", mocks.Now, ) mockHorizon.On( "LoadAccount", accountID, ).Return( horizon.AccountResponse{ AccountID: accountID, SequenceNumber: "10372672437354496", }, nil, ).Once() err := transactionSubmitter.InitAccount(seed) assert.Nil(t, err) txB64 := "AAAAAJbmB/pwwloZXCaCr9WR3Fue2lNhHGaDWKVOWO7MPq4QAAAAZAAk2eQAAAABAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAAd2/WGgaQ6CJcXQtGRFodrubQZ9ci5ZPRlxpqNPWV1CAAAAAAAAAAADuaygAAAAAAAAAAAcw+rhAAAABAyFjIMIZOtstCWtZlVBDj1AhTmsk5v1i2GGY4by2b5mgZoXXGgFTB8sfbQav0LzFKCcxY8h+9xPMT2e9xznAfDw==" // Persist sending transaction mockEntityManager.On( "Persist", mock.AnythingOfType("*entities.SentTransaction"), ).Return(nil).Once().Run(func(args mock.Arguments) { transaction := args.Get(0).(*entities.SentTransaction) assert.Equal(t, "4f885999be6ea7891052a53e496bcfb5c5a1a5bfb31923f649b028fdc74dd050", transaction.TransactionID) assert.Equal(t, "sending", string(transaction.Status)) assert.Equal(t, "GCLOMB72ODBFUGK4E2BK7VMR3RNZ5WSTMEOGNA2YUVHFR3WMH2XBAB6H", transaction.Source) assert.Equal(t, mocks.PredefinedTime, transaction.SubmittedAt) assert.Equal(t, txB64, transaction.EnvelopeXdr) }) // Persist failure mockEntityManager.On( "Persist", mock.AnythingOfType("*entities.SentTransaction"), ).Return(nil).Once().Run(func(args mock.Arguments) { transaction := args.Get(0).(*entities.SentTransaction) assert.Equal(t, "4f885999be6ea7891052a53e496bcfb5c5a1a5bfb31923f649b028fdc74dd050", transaction.TransactionID) assert.Equal(t, "failure", string(transaction.Status)) assert.Equal(t, "GCLOMB72ODBFUGK4E2BK7VMR3RNZ5WSTMEOGNA2YUVHFR3WMH2XBAB6H", transaction.Source) assert.Equal(t, mocks.PredefinedTime, transaction.SubmittedAt) assert.Equal(t, txB64, transaction.EnvelopeXdr) }) mockHorizon.On("SubmitTransaction", txB64).Return( horizon.SubmitTransactionResponse{ Ledger: nil, Extras: &horizon.SubmitTransactionResponseExtras{ ResultXdr: "AAAAAAAAAAD////7AAAAAA==", // tx_bad_seq }, }, nil, ).Once() // Updating sequence number mockHorizon.On( "LoadAccount", accountID, ).Return( horizon.AccountResponse{ AccountID: accountID, SequenceNumber: "100", }, nil, ).Once() _, err = transactionSubmitter.SubmitTransaction(seed, operation, nil) assert.Nil(t, err) assert.Equal(t, uint64(100), transactionSubmitter.Accounts[seed].SequenceNumber) mockHorizon.AssertExpectations(t) }) Convey("Successfully submits a transaction", func() { transactionSubmitter := NewTransactionSubmitter( mockHorizon, mockEntityManager, "Test SDF Network ; September 2015", mocks.Now, ) mockHorizon.On( "LoadAccount", accountID, ).Return( horizon.AccountResponse{ AccountID: accountID, SequenceNumber: "10372672437354496", }, nil, ).Once() err := transactionSubmitter.InitAccount(seed) assert.Nil(t, err) txB64 := "AAAAAJbmB/pwwloZXCaCr9WR3Fue2lNhHGaDWKVOWO7MPq4QAAAAZAAk2eQAAAABAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAAd2/WGgaQ6CJcXQtGRFodrubQZ9ci5ZPRlxpqNPWV1CAAAAAAAAAAADuaygAAAAAAAAAAAcw+rhAAAABAyFjIMIZOtstCWtZlVBDj1AhTmsk5v1i2GGY4by2b5mgZoXXGgFTB8sfbQav0LzFKCcxY8h+9xPMT2e9xznAfDw==" // Persist sending transaction mockEntityManager.On( "Persist", mock.AnythingOfType("*entities.SentTransaction"), ).Return(nil).Once().Run(func(args mock.Arguments) { transaction := args.Get(0).(*entities.SentTransaction) assert.Equal(t, "4f885999be6ea7891052a53e496bcfb5c5a1a5bfb31923f649b028fdc74dd050", transaction.TransactionID) assert.Equal(t, "sending", string(transaction.Status)) assert.Equal(t, "GCLOMB72ODBFUGK4E2BK7VMR3RNZ5WSTMEOGNA2YUVHFR3WMH2XBAB6H", transaction.Source) assert.Equal(t, mocks.PredefinedTime, transaction.SubmittedAt) assert.Equal(t, txB64, transaction.EnvelopeXdr) }) // Persist failure mockEntityManager.On( "Persist", mock.AnythingOfType("*entities.SentTransaction"), ).Return(nil).Once().Run(func(args mock.Arguments) { transaction := args.Get(0).(*entities.SentTransaction) assert.Equal(t, "4f885999be6ea7891052a53e496bcfb5c5a1a5bfb31923f649b028fdc74dd050", transaction.TransactionID) assert.Equal(t, "success", string(transaction.Status)) assert.Equal(t, "GCLOMB72ODBFUGK4E2BK7VMR3RNZ5WSTMEOGNA2YUVHFR3WMH2XBAB6H", transaction.Source) assert.Equal(t, mocks.PredefinedTime, transaction.SubmittedAt) assert.Equal(t, txB64, transaction.EnvelopeXdr) }) ledger := uint64(1486276) mockHorizon.On("SubmitTransaction", txB64).Return( horizon.SubmitTransactionResponse{Ledger: &ledger}, nil, ).Once() response, err := transactionSubmitter.SubmitTransaction(seed, operation, nil) assert.Nil(t, err) assert.Equal(t, *response.Ledger, ledger) assert.Equal(t, uint64(10372672437354497), transactionSubmitter.Accounts[seed].SequenceNumber) mockHorizon.AssertExpectations(t) }) }) Convey("Submits transaction with a memo", func() { operation := b.Payment( b.Destination{"GB3W7VQ2A2IOQIS4LUFUMRC2DWXONUDH24ROLE6RS4NGUNHVSXKCABOM"}, b.NativeAmount{"100"}, ) memo := b.MemoText{"Testing!"} Convey("Successfully submits a transaction", func() { transactionSubmitter := NewTransactionSubmitter( mockHorizon, mockEntityManager, "Test SDF Network ; September 2015", mocks.Now, ) mockHorizon.On( "LoadAccount", accountID, ).Return( horizon.AccountResponse{ AccountID: accountID, SequenceNumber: "10372672437354496", }, nil, ).Once() err := transactionSubmitter.InitAccount(seed) assert.Nil(t, err) txB64 := "AAAAAJbmB/pwwloZXCaCr9WR3Fue2lNhHGaDWKVOWO7MPq4QAAAAZAAk2eQAAAABAAAAAAAAAAEAAAAIVGVzdGluZyEAAAABAAAAAAAAAAEAAAAAd2/WGgaQ6CJcXQtGRFodrubQZ9ci5ZPRlxpqNPWV1CAAAAAAAAAAADuaygAAAAAAAAAAAcw+rhAAAABAU5ahFsd28sVKSUFcmAiEf+zSLXhf9HG/pJuQirR0s43zs7Y43vM8T3sIvJWHgwMADaZiy/D+evYWd/vS/uO8Ag==" // Persist sending transaction mockEntityManager.On( "Persist", mock.AnythingOfType("*entities.SentTransaction"), ).Return(nil).Once().Run(func(args mock.Arguments) { transaction := args.Get(0).(*entities.SentTransaction) assert.Equal(t, "60cb3c020b0c97352cbabdf68a822b04baea61927b0f1ac31260a9f8d0150316", transaction.TransactionID) assert.Equal(t, "sending", string(transaction.Status)) assert.Equal(t, "GCLOMB72ODBFUGK4E2BK7VMR3RNZ5WSTMEOGNA2YUVHFR3WMH2XBAB6H", transaction.Source) assert.Equal(t, mocks.PredefinedTime, transaction.SubmittedAt) assert.Equal(t, txB64, transaction.EnvelopeXdr) }) // Persist failure mockEntityManager.On( "Persist", mock.AnythingOfType("*entities.SentTransaction"), ).Return(nil).Once().Run(func(args mock.Arguments) { transaction := args.Get(0).(*entities.SentTransaction) assert.Equal(t, "60cb3c020b0c97352cbabdf68a822b04baea61927b0f1ac31260a9f8d0150316", transaction.TransactionID) assert.Equal(t, "success", string(transaction.Status)) assert.Equal(t, "GCLOMB72ODBFUGK4E2BK7VMR3RNZ5WSTMEOGNA2YUVHFR3WMH2XBAB6H", transaction.Source) assert.Equal(t, mocks.PredefinedTime, transaction.SubmittedAt) assert.Equal(t, txB64, transaction.EnvelopeXdr) }) ledger := uint64(1486276) mockHorizon.On("SubmitTransaction", txB64).Return( horizon.SubmitTransactionResponse{Ledger: &ledger}, nil, ).Once() response, err := transactionSubmitter.SubmitTransaction(seed, operation, memo) assert.Nil(t, err) assert.Equal(t, *response.Ledger, ledger) assert.Equal(t, uint64(10372672437354497), transactionSubmitter.Accounts[seed].SequenceNumber) mockHorizon.AssertExpectations(t) }) }) }) }) }