diff --git a/n_const.c b/n_const.c
index 3881d2f..41ce569 100644
--- a/n_const.c
+++ b/n_const.c
@@ -26,4 +26,5 @@ const char *c_cmd = "cmd";
const char *c_bad = "bad";
const char *c_iobad = "bad {io}";
const char *c_ioerr = "{io}";
+const char *c_unsupported = "{not-supported}";
const char *c_badbinerr = "{bad-bin}";
diff --git a/n_lib.h b/n_lib.h
index 6551b70..60d59b7 100644
--- a/n_lib.h
+++ b/n_lib.h
@@ -189,6 +189,9 @@ extern const char *c_iobad;
extern const char *c_ioerr;
#define c_ioerr_len 4
+extern const char *c_unsupported;
+#define c_unsupported_len 15
+
extern const char *c_badbinerr;
#define c_badbinerr_len 9
diff --git a/n_request.c b/n_request.c
index 078bac9..9226d17 100644
--- a/n_request.c
+++ b/n_request.c
@@ -256,7 +256,7 @@ J *NoteRequestResponseWithRetry(J *req, uint32_t timeoutSeconds)
rsp = NoteTransaction(req);
// Loop if there is no response, or if there is an io error
- if ( (rsp == NULL) || JContainsString(rsp, c_err, c_ioerr)) {
+ if ((rsp == NULL) || (JContainsString(rsp, c_err, c_ioerr) && !JContainsString(rsp, c_err, c_unsupported))) {
// Free error response
if (rsp != NULL) {
@@ -278,10 +278,6 @@ J *NoteRequestResponseWithRetry(J *req, uint32_t timeoutSeconds)
// Free the request
JDelete(req);
- if (rsp == NULL) {
- return NULL;
- }
-
// Return the response
return rsp;
}
@@ -637,8 +633,8 @@ J *noteTransactionShouldLock(J *req, bool lockNotecard)
bool isBadBin = false;
bool isIoError = false;
if (rsp != NULL) {
- isBadBin = NoteErrorContains(JGetString(rsp, c_err), c_badbinerr);
- isIoError = NoteErrorContains(JGetString(rsp, c_err), c_ioerr);
+ isBadBin = JContainsString(rsp, c_err, c_badbinerr);
+ isIoError = JContainsString(rsp, c_err, c_ioerr) && !JContainsString(rsp, c_err, c_unsupported);
} else {
// Failed to parse response as JSON
if (responseJSON == NULL) {
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 3caa539..9849431 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -59,6 +59,7 @@ endmacro(add_test)
add_test(NoteTransaction_test)
add_test(NoteRequest_test)
add_test(NoteRequestResponse_test)
+add_test(NoteRequestResponseWithRetry_test)
add_test(NoteRequestResponseJSON_test)
add_test(NoteRequestWithRetry_test)
add_test(NoteReset_test)
diff --git a/test/src/NoteRequestResponseWithRetry_test.cpp b/test/src/NoteRequestResponseWithRetry_test.cpp
new file mode 100644
index 0000000..8a7c4af
--- /dev/null
+++ b/test/src/NoteRequestResponseWithRetry_test.cpp
@@ -0,0 +1,229 @@
+/*!
+ * @file NoteRequestResponseWithRetry_test.cpp
+ *
+ * Written by the Blues Inc. team.
+ *
+ * Copyright (c) 2024 Blues Inc. MIT License. Use of this source code is
+ * governed by licenses granted by the copyright holder including that found in
+ * the
+ * LICENSE
+ * file.
+ *
+ */
+
+#include
+#include "fff.h"
+
+#include "n_lib.h"
+
+DEFINE_FFF_GLOBALS
+FAKE_VALUE_FUNC(J *, NoteTransaction, J *)
+FAKE_VALUE_FUNC(uint32_t, NoteGetMs)
+
+namespace
+{
+
+J *NoteTransactionIOError(J *)
+{
+ J *resp = JCreateObject();
+ assert(resp != NULL);
+ JAddStringToObject(resp, c_err, c_ioerr);
+
+ return resp;
+}
+
+J *NoteTransactionNotSupportedError(J *)
+{
+ J *resp = JCreateObject();
+ assert(resp != NULL);
+ JAddStringToObject(resp, c_err, c_unsupported);
+
+ return resp;
+}
+
+J *NoteTransactionIOAndNotSupportedErrors(J *)
+{
+ J *resp = JCreateObject();
+ assert(resp != NULL);
+ JAddStringToObject(resp, c_err, "{io} {not-supported}");
+
+ return resp;
+}
+
+J *NoteTransactionOtherError(J *)
+{
+ J *resp = JCreateObject();
+ assert(resp != NULL);
+ JAddStringToObject(resp, c_err, c_bad);
+
+ return resp;
+}
+
+TEST_CASE("NoteRequestResponseWithRetry")
+{
+ NoteSetFnDefault(malloc, free, NULL, NULL);
+
+ J *rsp = NULL;
+
+ GIVEN("A NULL request") {
+ J *req = NULL;
+
+ WHEN("NoteRequestResponseWithRetry is called") {
+ rsp = NoteRequestResponseWithRetry(req, 0);
+
+ THEN("The returned response is NULL") {
+ CHECK(rsp == NULL);
+ }
+ }
+ }
+
+ GIVEN("A valid request") {
+ J *req = NoteNewRequest("note.add");
+ REQUIRE(req != NULL);
+
+ AND_GIVEN("A timeout of 5 seconds") {
+ const uint32_t timeoutSec = 5;
+
+ AND_GIVEN("The timeout will trigger after 1 retry") {
+ uint32_t getMsReturnVals[3] = {0, 3000, 6000};
+ SET_RETURN_SEQ(NoteGetMs, getMsReturnVals, 3);
+
+ AND_GIVEN("The response to NoteTransaction is NULL") {
+ NoteTransaction_fake.return_val = NULL;
+
+ WHEN("NoteRequestResponseWithRetry is called") {
+ rsp = NoteRequestResponseWithRetry(req, timeoutSec);
+
+ THEN("The returned response is NULL") {
+ CHECK(rsp == NULL);
+ }
+
+ THEN("NoteTransaction will be called twice (1 retry)") {
+ CHECK(NoteTransaction_fake.call_count == 2);
+ }
+ }
+ }
+
+ AND_GIVEN("The response to NoteTransaction contains an I/O "
+ "error") {
+ NoteTransaction_fake.custom_fake = NoteTransactionIOError;
+
+ WHEN("NoteRequestResponseWithRetry is called") {
+ rsp = NoteRequestResponseWithRetry(req, timeoutSec);
+
+ THEN("The returned response is NULL") {
+ CHECK(rsp == NULL);
+ }
+
+ THEN("NoteTransaction will be called twice (1 "
+ "retry)") {
+ CHECK(NoteTransaction_fake.call_count == 2);
+ }
+ }
+ }
+
+ AND_GIVEN("The response to NoteTransaction contains a not "
+ "supported error") {
+ NoteTransaction_fake.custom_fake = NoteTransactionNotSupportedError;
+
+ WHEN("NoteRequestResponseWithRetry is called") {
+ rsp = NoteRequestResponseWithRetry(req, timeoutSec);
+
+ THEN("The returned response has a not-supported error") {
+ CHECK(JContainsString(rsp, c_err, c_unsupported));
+ }
+
+ THEN("NoteTransaction is only called once (no retries)") {
+ CHECK(NoteTransaction_fake.call_count == 1);
+ }
+ }
+ }
+
+ AND_GIVEN("The response to NoteTransaction contains a not "
+ "supported error AND an I/O error") {
+ NoteTransaction_fake.custom_fake = NoteTransactionIOAndNotSupportedErrors;
+
+ WHEN("NoteRequestResponseWithRetry is called") {
+ rsp = NoteRequestResponseWithRetry(req, timeoutSec);
+
+ THEN("The returned response has a not-supported "
+ "error") {
+ CHECK(JContainsString(rsp, c_err, c_unsupported));
+ }
+
+ THEN("The returned response has an I/O error") {
+ CHECK(JContainsString(rsp, c_err, c_ioerr));
+ }
+
+ THEN("NoteTransaction is only called once (no "
+ "retries)") {
+ CHECK(NoteTransaction_fake.call_count == 1);
+ }
+ }
+ }
+
+ AND_GIVEN("The response to NoteTransaction contains an error "
+ "that isn't I/O or \"not supported\"") {
+ NoteTransaction_fake.custom_fake = NoteTransactionOtherError;
+
+ WHEN("NoteRequestResponseWithRetry is called") {
+ rsp = NoteRequestResponseWithRetry(req, timeoutSec);
+
+ THEN("The returned response has the error") {
+ CHECK(JContainsString(rsp, c_err, c_bad));
+ }
+
+ THEN("NoteTransaction is only called once (no "
+ "retries)") {
+ CHECK(NoteTransaction_fake.call_count == 1);
+ }
+ }
+ }
+
+ AND_GIVEN("There's a valid response on the first "
+ " NoteTransaction attempt") {
+ NoteTransaction_fake.return_val = JCreateObject();
+
+ WHEN("NoteRequestResponseWithRetry is called") {
+ rsp = NoteRequestResponseWithRetry(req, timeoutSec);
+
+ THEN("The returned response is non-NULL") {
+ CHECK(rsp != NULL);
+ }
+
+ THEN("NoteTranaction is only called once (no "
+ "retries)") {
+ CHECK(NoteTransaction_fake.call_count == 1);
+ }
+ }
+ }
+
+ AND_GIVEN("There's a valid response on the second "
+ "NoteTransaction attempt") {
+ J *noteTransactionReturnVals[2] = {NULL, JCreateObject()};
+ SET_RETURN_SEQ(NoteTransaction, noteTransactionReturnVals,
+ 2);
+
+ WHEN("NoteRequestResponseWithRetry is called") {
+ rsp = NoteRequestResponseWithRetry(req, timeoutSec);
+
+ THEN("The returned response is non-NULL") {
+ CHECK(rsp != NULL);
+ }
+
+ THEN("NoteTranaction is only called twice (1 retry)") {
+ CHECK(NoteTransaction_fake.call_count == 2);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ JDelete(rsp);
+
+ RESET_FAKE(NoteTransaction);
+ RESET_FAKE(NoteGetMs);
+}
+
+}
diff --git a/test/src/NoteTransaction_test.cpp b/test/src/NoteTransaction_test.cpp
index a9e8794..9787c64 100644
--- a/test/src/NoteTransaction_test.cpp
+++ b/test/src/NoteTransaction_test.cpp
@@ -3,7 +3,7 @@
*
* Written by the Blues Inc. team.
*
- * Copyright (c) 2023 Blues Inc. MIT License. Use of this source code is
+ * Copyright (c) 2024 Blues Inc. MIT License. Use of this source code is
* governed by licenses granted by the copyright holder including that found in
* the
* LICENSE
@@ -11,8 +11,6 @@
*
*/
-
-
#include
#include "fff.h"
@@ -68,6 +66,19 @@ const char *noteJSONTransactionIOError(const char *, size_t, char **resp, uint32
return NULL;
}
+const char *noteJSONTransactionNotSupportedError(const char *, size_t, char **resp, uint32_t)
+{
+ static char respString[] = "{\"err\": \"{not-supported}\"}";
+
+ if (resp) {
+ char* respBuf = reinterpret_cast(malloc(sizeof(respString)));
+ memcpy(respBuf, respString, sizeof(respString));
+ *resp = respBuf;
+ }
+
+ return NULL;
+}
+
SCENARIO("NoteTransaction")
{
NoteSetFnDefault(malloc, free, NULL, NULL);
@@ -214,6 +225,32 @@ SCENARIO("NoteTransaction")
JDelete(resp);
}
+ GIVEN("A valid request") {
+ J *req = NoteNewRequest("note.add");
+ REQUIRE(req != NULL);
+
+ AND_GIVEN("noteJSONTransaction returns a response with a \"not "
+ "supported\" error") {
+ noteJSONTransaction_fake.custom_fake = noteJSONTransactionNotSupportedError;
+
+ WHEN("NoteTransaction is called") {
+ J *rsp = NoteTransaction(req);
+
+ THEN("The response contains the \"not supported\" error") {
+ CHECK(JContainsString(rsp, c_err, c_unsupported));
+ }
+
+ THEN("noteJSONTransaction is only called once (no retries)") {
+ CHECK(noteJSONTransaction_fake.call_count == 1);
+ }
+
+ JDelete(rsp);
+ }
+ }
+
+ JDelete(req);
+ }
+
SECTION("A reset is required and it fails") {
J *req = NoteNewRequest("note.add");
REQUIRE(req != NULL);
@@ -407,5 +444,3 @@ SCENARIO("NoteTransaction")
}
}
-
-