tests: Add ztest tests for coap client

Add tests for coap client and stubs for isolating the tests.

Signed-off-by: Jarno Lämsä <jarno.lamsa@nordicsemi.no>
diff --git a/tests/net/lib/coap_client/CMakeLists.txt b/tests/net/lib/coap_client/CMakeLists.txt
new file mode 100644
index 0000000..60506c2
--- /dev/null
+++ b/tests/net/lib/coap_client/CMakeLists.txt
@@ -0,0 +1,28 @@
+# SPDX-License-Identifier: Apache-2.0
+
+cmake_minimum_required(VERSION 3.20.0)
+find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
+project(coap_client_test)
+
+set(APP_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src)
+
+# Add test sources
+target_sources(app PRIVATE ${APP_SRC_DIR}/main.c)
+target_sources(app PRIVATE ${APP_SRC_DIR}/stubs.c)
+target_sources(app PRIVATE ${ZEPHYR_BASE}/subsys/net/lib/coap/coap_client.c)
+target_sources(app PRIVATE ${ZEPHYR_BASE}/subsys/net/lib/coap/coap.c)
+
+# Add includes directories
+target_include_directories(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
+target_include_directories(app PRIVATE ${ZEPHYR_BASE}/include/)
+
+add_compile_definitions(CONFIG_NET_SOCKETS_POLL_MAX=3)
+add_compile_definitions(CONFIG_COAP_CLIENT=y)
+add_compile_definitions(CONFIG_COAP_CLIENT_BLOCK_SIZE=256)
+add_compile_definitions(CONFIG_COAP_CLIENT_MESSAGE_SIZE=256)
+add_compile_definitions(CONFIG_COAP_CLIENT_MESSAGE_HEADER_SIZE=48)
+add_compile_definitions(CONFIG_COAP_CLIENT_STACK_SIZE=1024)
+add_compile_definitions(CONFIG_COAP_CLIENT_THREAD_PRIORITY=10)
+add_compile_definitions(CONFIG_COAP_LOG_LEVEL=4)
+add_compile_definitions(CONFIG_NET_SOCKETS_POSIX_NAMES=y)
+add_compile_definitions(CONFIG_COAP_INIT_ACK_TIMEOUT_MS=2000)
diff --git a/tests/net/lib/coap_client/prj.conf b/tests/net/lib/coap_client/prj.conf
new file mode 100644
index 0000000..d185b3e
--- /dev/null
+++ b/tests/net/lib/coap_client/prj.conf
@@ -0,0 +1,4 @@
+#Testing
+CONFIG_ZTEST=y
+CONFIG_ZTEST_NEW_API=y
+CONFIG_ZTEST_STACK_SIZE=4096
diff --git a/tests/net/lib/coap_client/src/main.c b/tests/net/lib/coap_client/src/main.c
new file mode 100644
index 0000000..8cfc70a
--- /dev/null
+++ b/tests/net/lib/coap_client/src/main.c
@@ -0,0 +1,354 @@
+/*
+ * Copyright (c) 2023 Nordic Semiconductor ASA
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <zephyr/fff.h>
+#include <zephyr/logging/log.h>
+#include <zephyr/ztest.h>
+
+#include "stubs.h"
+
+LOG_MODULE_REGISTER(coap_client_test);
+
+DEFINE_FFF_GLOBALS;
+#define FFF_FAKES_LIST(FAKE)
+
+static uint8_t last_response_code;
+static const char *test_path = "test";
+static uint16_t last_message_id;
+
+static struct coap_client client;
+
+static char *short_payload = "testing";
+static char *long_payload = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "
+				  "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut "
+				  "enim ad minim veniam, quis nostrud exercitation ullamco laboris "
+				  "nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor "
+				  "in reprehenderit in voluptate velit esse cillum dolore eu fugiat"
+				  " nulla pariatur. Excepteur sint occaecat cupidatat non proident,"
+				  " sunt in culpa qui officia deserunt mollit anim id est laborum.";
+
+static ssize_t z_impl_zsock_recvfrom_custom_fake(int sock, void *buf, size_t max_len, int flags,
+					  struct sockaddr *src_addr, socklen_t *addrlen)
+{
+	LOG_INF("Recvfrom");
+	static uint8_t ack_data[] = {
+		0x68, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+	};
+
+	ack_data[2] = (uint8_t) last_message_id >> 8;
+	ack_data[3] = (uint8_t) last_message_id;
+
+	memcpy(buf, ack_data, sizeof(ack_data));
+
+	return sizeof(ack_data);
+}
+
+static ssize_t z_impl_zsock_sendto_custom_fake(int sock, void *buf, size_t len,
+			       int flags, const struct sockaddr *dest_addr,
+			       socklen_t addrlen)
+{
+	LOG_INF("Sendto");
+	last_message_id = 0;
+	last_message_id |= ((uint8_t *) buf)[2] << 8;
+	last_message_id |= ((uint8_t *) buf)[3];
+
+	last_response_code = ((uint8_t *) buf)[1];
+
+	LOG_INF("Latest message ID: %d", last_message_id);
+	return 1;
+}
+
+static ssize_t z_impl_zsock_recvfrom_custom_fake_response(int sock, void *buf, size_t max_len,
+							  int flags, struct sockaddr *src_addr,
+							  socklen_t *addrlen)
+{
+	static uint8_t ack_data[] = {
+		0x48, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+	};
+
+	ack_data[2] = (uint8_t) last_message_id >> 8;
+	ack_data[3] = (uint8_t) last_message_id;
+
+	memcpy(buf, ack_data, sizeof(ack_data));
+
+	return sizeof(ack_data);
+}
+
+static ssize_t z_impl_zsock_recvfrom_custom_fake_empty_ack(int sock, void *buf, size_t max_len,
+							   int flags, struct sockaddr *src_addr,
+							   socklen_t *addrlen)
+{
+	static uint8_t ack_data[] = {
+		0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+	};
+
+	ack_data[2] = (uint8_t) last_message_id >> 8;
+	ack_data[3] = (uint8_t) last_message_id;
+
+	memcpy(buf, ack_data, sizeof(ack_data));
+
+	z_impl_zsock_recvfrom_fake.custom_fake = z_impl_zsock_recvfrom_custom_fake_response;
+
+	return sizeof(ack_data);
+}
+
+static ssize_t z_impl_zsock_recvfrom_custom_fake_unmatching(int sock, void *buf, size_t max_len,
+							    int flags, struct sockaddr *src_addr,
+							    socklen_t *addrlen)
+{
+	static uint8_t ack_data[] = {
+		0x68, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
+	};
+
+	ack_data[2] = (uint8_t) last_message_id >> 8;
+	ack_data[3] = (uint8_t) last_message_id;
+
+	memcpy(buf, ack_data, sizeof(ack_data));
+
+	return sizeof(ack_data);
+}
+
+static void *suite_setup(void)
+{
+	coap_client_init(&client, NULL);
+
+	return NULL;
+}
+
+static void test_setup(void *data)
+{
+	/* Register resets */
+	DO_FOREACH_FAKE(RESET_FAKE);
+	/* reset common FFF internal structures */
+	FFF_RESET_HISTORY();
+
+	z_impl_zsock_recvfrom_fake.custom_fake = z_impl_zsock_recvfrom_custom_fake;
+	z_impl_zsock_sendto_fake.custom_fake = z_impl_zsock_sendto_custom_fake;
+}
+
+void coap_callback(int16_t code, size_t offset, const uint8_t *payload, size_t len, bool last_block,
+		   void *user_data)
+{
+	LOG_INF("CoAP response callback, %d", code);
+	last_response_code = code;
+}
+
+ZTEST_SUITE(coap_client, NULL, suite_setup, test_setup, NULL, NULL);
+
+ZTEST(coap_client, test_get_request)
+{
+	int ret = 0;
+	struct sockaddr address;
+	struct coap_client_request client_request = {
+		.method = COAP_METHOD_GET,
+		.confirmable = true,
+		.path = test_path,
+		.fmt = COAP_CONTENT_FORMAT_TEXT_PLAIN,
+		.cb = coap_callback,
+		.payload = NULL,
+		.len = 0
+	};
+
+	client_request.payload = short_payload;
+	client_request.len = strlen(short_payload);
+
+	k_sleep(K_MSEC(1));
+
+	LOG_INF("Send request");
+	ret = coap_client_req(&client, 0, &address, &client_request, -1);
+	zassert_true(ret >= 0, "Sending request failed, %d", ret);
+	set_socket_events(ZSOCK_POLLIN);
+
+	k_sleep(K_MSEC(5));
+	k_sleep(K_MSEC(1000));
+	zassert_equal(last_response_code, COAP_RESPONSE_CODE_OK, "Unexpected response");
+}
+
+ZTEST(coap_client, test_get_no_path)
+{
+	int ret = 0;
+	struct sockaddr address;
+	struct coap_client_request client_request = {
+		.method = COAP_METHOD_GET,
+		.confirmable = true,
+		.path = NULL,
+		.fmt = COAP_CONTENT_FORMAT_TEXT_PLAIN,
+		.cb = coap_callback,
+		.payload = NULL,
+		.len = 0
+	};
+
+	client_request.payload = short_payload;
+	client_request.len = strlen(short_payload);
+
+	k_sleep(K_MSEC(1));
+
+	LOG_INF("Send request");
+	ret = coap_client_req(&client, 0, &address, &client_request, -1);
+
+	zassert_equal(ret, -EINVAL, "Get request without path");
+}
+
+ZTEST(coap_client, test_send_large_data)
+{
+	int ret = 0;
+	struct sockaddr address;
+	struct coap_client_request client_request = {
+		.method = COAP_METHOD_GET,
+		.confirmable = true,
+		.path = test_path,
+		.fmt = COAP_CONTENT_FORMAT_TEXT_PLAIN,
+		.cb = coap_callback,
+		.payload = NULL,
+		.len = 0
+	};
+
+	client_request.payload = long_payload;
+	client_request.len = strlen(long_payload);
+
+	k_sleep(K_MSEC(1));
+
+	LOG_INF("Send request");
+	ret = coap_client_req(&client, 0, &address, &client_request, -1);
+	zassert_true(ret >= 0, "Sending request failed, %d", ret);
+	set_socket_events(ZSOCK_POLLIN);
+
+	k_sleep(K_MSEC(5));
+	k_sleep(K_MSEC(1000));
+	zassert_equal(last_response_code, COAP_RESPONSE_CODE_OK, "Unexpected response");
+}
+
+ZTEST(coap_client, test_no_response)
+{
+	int ret = 0;
+	struct sockaddr address;
+	struct coap_client_request client_request = {
+		.method = COAP_METHOD_GET,
+		.confirmable = true,
+		.path = test_path,
+		.fmt = COAP_CONTENT_FORMAT_TEXT_PLAIN,
+		.cb = coap_callback,
+		.payload = NULL,
+		.len = 0
+	};
+
+	client_request.payload = short_payload;
+	client_request.len = strlen(short_payload);
+
+	k_sleep(K_MSEC(1));
+
+	LOG_INF("Send request");
+	clear_socket_events();
+	ret = coap_client_req(&client, 0, &address, &client_request, -1);
+
+	zassert_true(ret >= 0, "Sending request failed, %d", ret);
+	k_sleep(K_MSEC(1000));
+}
+
+ZTEST(coap_client, test_separate_response)
+{
+	int ret = 0;
+	struct sockaddr address;
+	struct coap_client_request client_request = {
+		.method = COAP_METHOD_GET,
+		.confirmable = true,
+		.path = test_path,
+		.fmt = COAP_CONTENT_FORMAT_TEXT_PLAIN,
+		.cb = coap_callback,
+		.payload = NULL,
+		.len = 0
+	};
+
+	client_request.payload = short_payload;
+	client_request.len = strlen(short_payload);
+
+	z_impl_zsock_recvfrom_fake.custom_fake = z_impl_zsock_recvfrom_custom_fake_empty_ack;
+
+	k_sleep(K_MSEC(1));
+
+	LOG_INF("Send request");
+	ret = coap_client_req(&client, 0, &address, &client_request, -1);
+	zassert_true(ret >= 0, "Sending request failed, %d", ret);
+	set_socket_events(ZSOCK_POLLIN);
+
+	k_sleep(K_MSEC(5));
+	k_sleep(K_MSEC(1000));
+
+	k_sleep(K_MSEC(1000));
+
+	zassert_equal(last_response_code, COAP_RESPONSE_CODE_OK, "Unexpected response");
+}
+
+ZTEST(coap_client, test_multiple_requests)
+{
+	int ret = 0;
+	struct sockaddr address;
+	struct coap_client_request client_request = {
+		.method = COAP_METHOD_GET,
+		.confirmable = true,
+		.path = test_path,
+		.fmt = COAP_CONTENT_FORMAT_TEXT_PLAIN,
+		.cb = coap_callback,
+		.payload = NULL,
+		.len = 0
+	};
+
+	client_request.payload = short_payload;
+	client_request.len = strlen(short_payload);
+
+	k_sleep(K_MSEC(1));
+	set_socket_events(ZSOCK_POLLIN);
+
+	LOG_INF("Send request");
+	ret = coap_client_req(&client, 0, &address, &client_request, -1);
+	zassert_true(ret >= 0, "Sending request failed, %d", ret);
+
+	ret = coap_client_req(&client, 0, &address, &client_request, -1);
+	zassert_equal(ret, -EAGAIN, "Shouldn't be able to send 2 requests at same time");
+
+	k_sleep(K_MSEC(5));
+	k_sleep(K_MSEC(1000));
+	zassert_equal(last_response_code, COAP_RESPONSE_CODE_OK, "Unexpected response");
+
+	ret = coap_client_req(&client, 0, &address, &client_request, -1);
+	zassert_true(ret >= 0, "Sending request failed, %d", ret);
+
+	k_sleep(K_MSEC(5));
+	k_sleep(K_MSEC(1000));
+	zassert_equal(last_response_code, COAP_RESPONSE_CODE_OK, "Unexpected response");
+}
+
+ZTEST(coap_client, test_unmatching_tokens)
+{
+	int ret = 0;
+	struct sockaddr address;
+	struct coap_client_request client_request = {
+		.method = COAP_METHOD_GET,
+		.confirmable = true,
+		.path = test_path,
+		.fmt = COAP_CONTENT_FORMAT_TEXT_PLAIN,
+		.cb = coap_callback,
+		.payload = NULL,
+		.len = 0
+	};
+
+	client_request.payload = short_payload;
+	client_request.len = strlen(short_payload);
+
+	z_impl_zsock_recvfrom_fake.custom_fake = z_impl_zsock_recvfrom_custom_fake_unmatching;
+
+	LOG_INF("Send request");
+	ret = coap_client_req(&client, 0, &address, &client_request, 0);
+	zassert_true(ret >= 0, "Sending request failed, %d", ret);
+	set_socket_events(ZSOCK_POLLIN);
+
+	k_sleep(K_MSEC(1));
+	k_sleep(K_MSEC(1));
+	clear_socket_events();
+	zassert_equal(last_response_code, COAP_RESPONSE_CODE_NOT_FOUND, "Unexpected response %d",
+		      last_response_code);
+	k_sleep(K_MSEC(1));
+}
diff --git a/tests/net/lib/coap_client/src/stubs.c b/tests/net/lib/coap_client/src/stubs.c
new file mode 100644
index 0000000..13effd8
--- /dev/null
+++ b/tests/net/lib/coap_client/src/stubs.c
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2023 Nordic Semiconductor ASA
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <zephyr/logging/log.h>
+#include <stubs.h>
+
+LOG_MODULE_DECLARE(coap_client_test);
+
+DEFINE_FAKE_VALUE_FUNC(uint32_t, z_impl_sys_rand32_get);
+DEFINE_FAKE_VOID_FUNC(z_impl_sys_rand_get, void *, size_t);
+DEFINE_FAKE_VALUE_FUNC(ssize_t, z_impl_zsock_recvfrom, int, void *, size_t, int, struct sockaddr *,
+		       socklen_t *);
+DEFINE_FAKE_VALUE_FUNC(ssize_t, z_impl_zsock_sendto, int, void*, size_t, int,
+		       const struct sockaddr *, socklen_t);
+
+struct zsock_pollfd {
+	int fd;
+	short events;
+	short revents;
+};
+
+static short my_events;
+
+void set_socket_events(short events)
+{
+	my_events |= events;
+}
+
+void clear_socket_events(void)
+{
+	my_events = 0;
+}
+
+int z_impl_zsock_socket(int family, int type, int proto)
+{
+	return 0;
+}
+
+int z_impl_zsock_poll(struct zsock_pollfd *fds, int nfds, int poll_timeout)
+{
+	LOG_INF("Polling, events %d", my_events);
+	k_sleep(K_MSEC(10));
+	fds->revents = my_events;
+	if (my_events) {
+		return 1;
+	} else {
+		return 0;
+	}
+}
diff --git a/tests/net/lib/coap_client/src/stubs.h b/tests/net/lib/coap_client/src/stubs.h
new file mode 100644
index 0000000..eb340d9
--- /dev/null
+++ b/tests/net/lib/coap_client/src/stubs.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2023 Nordic Semiconductor ASA
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef STUBS_H
+#define STUBS_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <zephyr/fff.h>
+#include <zephyr/ztest.h>
+
+#include <zephyr/net/coap_client.h>
+
+#define ZSOCK_POLLIN  1
+#define ZSOCK_POLLOUT 4
+
+void set_socket_events(short events);
+void clear_socket_events(void);
+
+DECLARE_FAKE_VALUE_FUNC(uint32_t, z_impl_sys_rand32_get);
+DECLARE_FAKE_VOID_FUNC(z_impl_sys_rand_get, void *, size_t);
+DECLARE_FAKE_VALUE_FUNC(ssize_t, z_impl_zsock_recvfrom, int, void *, size_t, int, struct sockaddr *,
+			socklen_t *);
+DECLARE_FAKE_VALUE_FUNC(ssize_t, z_impl_zsock_sendto, int, void*, size_t, int,
+			const struct sockaddr *, socklen_t);
+
+#define DO_FOREACH_FAKE(FUNC)                                                                      \
+	do {                                                                                       \
+		FUNC(z_impl_sys_rand32_get)                                                        \
+		FUNC(z_impl_sys_rand_get)                                                          \
+		FUNC(z_impl_zsock_recvfrom)                                                        \
+		FUNC(z_impl_zsock_sendto)                                                          \
+	} while (0)
+
+#endif /* STUBS_H */
diff --git a/tests/net/lib/coap_client/testcase.yaml b/tests/net/lib/coap_client/testcase.yaml
new file mode 100644
index 0000000..923e138
--- /dev/null
+++ b/tests/net/lib/coap_client/testcase.yaml
@@ -0,0 +1,6 @@
+common:
+  depends_on: netif
+tests:
+  net.coap.client:
+    platform_allow: native_posix
+    tags: coap net