OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-cogserver / tests / http / HttpUTest.cxxtest

242 lines · cxxtest ref:
/*
 * tests/http/HttpUTest.cxxtest
 *
 * Copyright (C) 2025 BrainyBlaze Dynamics Inc.
 * All Rights Reserved
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License v3 as
 * published by the Free Software Foundation and including the exceptions
 * at http://opencog.org/wiki/Licenses
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses/
 */

#include <thread>
#include <unistd.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>

#include <opencog/atomspace/AtomSpace.h>
#include <opencog/atoms/atom_types/atom_names.h>
#include <opencog/atoms/value/FloatValue.h>
#include <opencog/atoms/value/VoidValue.h>
#include <opencog/cogserver/atoms/CogServerNode.h>

using namespace opencog;

class HttpUTest : public CxxTest::TestSuite
{
private:
	AtomSpacePtr _asp;
	CogServerNodePtr _cogserver;

	// Simple TCP client connection
	int connect_to_server(int port)
	{
		int sockfd = socket(AF_INET, SOCK_STREAM, 0);
		if (sockfd < 0) {
			TS_FAIL("ERROR opening socket");
			return -1;
		}

		struct sockaddr_in serv_addr;
		memset(&serv_addr, 0, sizeof(serv_addr));
		serv_addr.sin_family = AF_INET;
		serv_addr.sin_port = htons(port);
		serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

		if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
			close(sockfd);
			TS_FAIL("ERROR connecting to server");
			return -1;
		}

		return sockfd;
	}

	// Send data and receive response
	std::string send_and_receive(int sockfd, const std::string& data)
	{
		// Send the request
		if (send(sockfd, data.c_str(), data.length(), 0) < 0) {
			TS_FAIL("ERROR writing to socket");
			return "";
		}

		// Read the response
		char buffer[4096];
		memset(buffer, 0, sizeof(buffer));
		int n = recv(sockfd, buffer, sizeof(buffer)-1, 0);
		if (n < 0) {
			TS_FAIL("ERROR reading from socket");
			return "";
		}

		return std::string(buffer, n);
	}

public:
	HttpUTest()
	{
		// logger().set_level(Logger::DEBUG);
		logger().set_level(Logger::INFO);
		logger().set_print_to_stdout_flag(true);

		// Create the AtomSpace and CogServerNode
		_asp = createAtomSpace();
		Handle hcsn = _asp->add_node(COG_SERVER_NODE, "test-cogserver");
		_cogserver = CogServerNodeCast(hcsn);

		// Set ports and start the server (web only)
		_cogserver->setValue(_asp->add_atom(Predicate("*-telnet-port-*")),
		                     createFloatValue(0.0));
		_cogserver->setValue(_asp->add_atom(Predicate("*-web-port-*")),
		                     createFloatValue(18181.0));
		_cogserver->setValue(_asp->add_atom(Predicate("*-mcp-port-*")),
		                     createFloatValue(0.0));
		_cogserver->setValue(_asp->add_atom(Predicate("*-start-*")),
		                     createVoidValue());
	}

	~HttpUTest()
	{
		// Stop the server
		_cogserver->setValue(_asp->add_atom(Predicate("*-stop-*")),
		                     createVoidValue());
		_cogserver = nullptr;
		_asp = nullptr;

		// erase the log file if no assertions failed
		if (!CxxTest::TestTracker::tracker().suiteFailed())
			std::remove(logger().get_filename().c_str());
	}

	void setUp()
	{
	}

	void tearDown()
	{
	}

	// Test basic HTTP GET request to root
	void test_http_get_root()
	{
		int sockfd = connect_to_server(18181);
		TS_ASSERT_LESS_THAN(0, sockfd);

		// Send HTTP GET request for root
		std::string request = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";
		std::string response = send_and_receive(sockfd, request);

		close(sockfd);

		// Check that we got a response
		TS_ASSERT(!response.empty());

		// Check for HTTP/1.1 404 Not Found (since root doesn't exist)
		TS_ASSERT(response.find("HTTP/1.1 404 Not Found") != std::string::npos);

		// Check for proper headers
		TS_ASSERT(response.find("Content-Type: text/html") != std::string::npos);
		TS_ASSERT(response.find("Server: CogServer") != std::string::npos);

		// Check that there's a body after the headers
		size_t body_start = response.find("\r\n\r\n");
		TS_ASSERT(body_start != std::string::npos);

		// Check that the body looks like HTML
		std::string body = response.substr(body_start + 4);
		TS_ASSERT(!body.empty());
		TS_ASSERT(body.find("<html>") != std::string::npos ||
		          body.find("<HTML>") != std::string::npos ||
		          body.find("<!DOCTYPE") != std::string::npos);
	}

	// Test HTTP GET request for /stats
	void test_http_get_stats()
	{
		int sockfd = connect_to_server(18181);
		TS_ASSERT_LESS_THAN(0, sockfd);

		// Send HTTP GET request for /stats
		std::string request = "GET /stats HTTP/1.1\r\nHost: localhost\r\n\r\n";
		std::string response = send_and_receive(sockfd, request);

		close(sockfd);

		// Check that we got a response
		TS_ASSERT(!response.empty());

		// Check for HTTP/1.1 200 OK
		TS_ASSERT(response.find("HTTP/1.1 200 OK") != std::string::npos);

		// Check for proper headers
		TS_ASSERT(response.find("Content-Type: text/html") != std::string::npos);
		TS_ASSERT(response.find("Server: CogServer") != std::string::npos);

		// Check that there's a body after the headers
		size_t body_start = response.find("\r\n\r\n");
		TS_ASSERT(body_start != std::string::npos);

		// Check that the body looks like HTML
		std::string body = response.substr(body_start + 4);
		TS_ASSERT(!body.empty());
		TS_ASSERT(body.find("<html>") != std::string::npos ||
		          body.find("<!DOCTYPE html>") != std::string::npos);

		// Stats page should contain network statistics
		TS_ASSERT(body.find("Network Activity") != std::string::npos ||
		          body.find("CogServer Stats") != std::string::npos ||
		          body.find("socket") != std::string::npos);
	}

	// Test HTTP POST request (should return 501 Not Implemented)
	void test_http_post()
	{
		int sockfd = connect_to_server(18181);
		TS_ASSERT_LESS_THAN(0, sockfd);

		// Send HTTP POST request
		std::string request = "POST /test HTTP/1.1\r\nHost: localhost\r\nContent-Length: 0\r\n\r\n";
		std::string response = send_and_receive(sockfd, request);

		close(sockfd);

		// Check that we got a response
		TS_ASSERT(!response.empty());

		// WebServer uses GET/POST to distinguish requests, so this should work
		// It should return 404 for unknown URL
		TS_ASSERT(response.find("HTTP/1.1 404 Not Found") != std::string::npos ||
		          response.find("HTTP/1.1 501 Not Implemented") != std::string::npos);
	}

	// Test malformed HTTP request
	void test_http_malformed()
	{
		int sockfd = connect_to_server(18181);
		TS_ASSERT_LESS_THAN(0, sockfd);

		// Send malformed request (not HTTP)
		std::string request = "INVALID REQUEST\r\n\r\n";
		std::string response = send_and_receive(sockfd, request);

		close(sockfd);

		// Should get HTTP 501 Not Implemented
		TS_ASSERT(response.find("HTTP/1.1 501 Not Implemented") != std::string::npos);
	}
};