more gtests and fix piauth

This commit is contained in:
2026-09-24 11:49:36 +03:00
parent 9e663a7501
commit 64a9fc3dae
4 changed files with 536 additions and 13 deletions
+1 -1
View File
@@ -231,7 +231,6 @@ PIAuth::State PIAuth::receive(PIByteArray & ba) {
return state;
}
if ((state == KeyExchange && rstate == Connected) || (state == Connected && rstate == Connected)) {
ba.clear();
PIByteArray rinfo;
ba >> rinfo;
bool ok = false;
@@ -239,6 +238,7 @@ PIAuth::State PIAuth::receive(PIByteArray & ba) {
if (!ok) return disconnect(ba, "Error while exchange keys");
state = Connected;
connected(PIString::fromUTF8(rinfo));
ba.clear();
ba << (int)state << crypt.generateRandomBuff(randomi() % PIAUTH_NOISE_MAX_SIZE);
return state;
}
+3
View File
@@ -48,3 +48,6 @@ endif()
if(TARGET pip_http_server AND TARGET pip_http_client)
pip_test(http_server pip_http_server pip_http_client)
endif()
if(TARGET pip_crypt)
pip_test(crypt pip_crypt)
endif()
+20 -12
View File
@@ -3,11 +3,19 @@
#include "gtest/gtest.h"
//! \~english Each test binds its own port so parallel ctest shards (which run
//! every TEST case in a separate process) never contend for the same socket.
//! \~russian Каждый тест слушает свой порт, чтобы параллельные ctest shards
//! (запускающие каждый TEST в отдельном процессе) не конфликтовали за сокет.
constexpr ushort OneClientPort = 12345;
constexpr ushort ManyClientsPort = 12346;
constexpr ushort DynamicClientsPort = 12347;
template<bool WithConnectPing = false, bool WithPong = false, ullong WriteSize = 64_KiB>
PIClientServer::Server * createServer() {
PIClientServer::Server * createServer(ushort port) {
auto s = new PIClientServer::Server();
s->setClientFactory([] { return new TestServerClient<WithConnectPing, WithPong, WriteSize>(); });
s->listenAll(12345);
s->listenAll(port);
return s;
}
@@ -23,17 +31,17 @@ bool waitLoop(std::function<bool()> exit_loop, const PISystemTime & timeout) {
}
template<typename Client>
Client * createAndConnectClient() {
Client * createAndConnectClient(ushort port) {
auto c = new Client();
c->connect(PINetworkAddress::resolve("127.0.0.1", 12345));
c->connect(PINetworkAddress::resolve("127.0.0.1", port));
return c;
}
TEST(ClientServer, OneClient) {
auto const loop_timeout = 1000_ms;
auto s = createServer<false, true>();
auto s = createServer<false, true>(OneClientPort);
piMinSleep();
auto c = createAndConnectClient<TestClient<false, false>>();
auto c = createAndConnectClient<TestClient<false, false>>(OneClientPort);
waitLoop([s]() { return s->clientsCount() > 0; }, loop_timeout);
EXPECT_EQ(1, s->clientsCount());
@@ -59,8 +67,8 @@ class ClientSendThread {
using ClientType = TestClient<false, false, 10_KiB>;
public:
ClientSendThread() {
client = createAndConnectClient<ClientType>();
explicit ClientSendThread(ushort port) {
client = createAndConnectClient<ClientType>(port);
sendThread.setName("clSend");
}
@@ -106,11 +114,11 @@ TEST(ClientServer, ManyClients) {
auto const loop_timeout = 1_s;
constexpr int clients_count = 20;
PIVector<ClientSendThread *> clients;
auto s = createServer<false, false, 100_KiB>();
auto s = createServer<false, false, 100_KiB>(ManyClientsPort);
piMinSleep();
piForTimes(clients_count) {
clients.append(new ClientSendThread());
clients.append(new ClientSendThread(ManyClientsPort));
}
EXPECT_EQ(clients_count, clients.size_s());
@@ -154,11 +162,11 @@ TEST(ClientServer, DynamicClients) {
constexpr int clients_count = 20;
PIVector<ClientSendThread *> clients;
PIMutex clients_mutex;
auto s = createServer<true, true, 10_KiB>();
auto s = createServer<true, true, 10_KiB>(DynamicClientsPort);
const auto spawnClient = [&clients, &clients_mutex]() {
// if (clients.size() > 100) return;
auto c = new ClientSendThread();
auto c = new ClientSendThread(DynamicClientsPort);
c->startSend();
clients_mutex.lock();
clients << c;
+512
View File
@@ -0,0 +1,512 @@
#include "piauth.h"
#include "picrypt.h"
#include "piliterals_string.h"
#include "gtest/gtest.h"
//! \~english Tests for the PIAuth signed key-exchange state machine.
//! \~russian Тесты машины состояний аутентификации PIAuth с подписанным обменом ключами.
//!
//! The protocol is driven in-process: two \c PIAuth endpoints exchange the same
//! \c PIByteArray packets the network transport would carry. Each \a receive()
//! updates the endpoint state and writes the reply back into the passed buffer.
//! \~russian Протокол прогоняется в одном процессе: две конечные точки \c PIAuth
//! обмениваются теми же пакетами \c PIByteArray, которые переносил бы транспорт.
//! Каждый \a receive() обновляет состояние и записывает ответ обратно в буфер.
namespace {
//! \~english Deterministic signing secret key for a human-readable seed.
//! \~russian Детерминированный секретный ключ подписи по текстовому seed.
PIByteArray signKey(const char * seed) {
return PIAuth::generateSign(PIByteArray::fromAscii(seed));
}
//! \~english One-element authorized public key list.
//! \~russian Список доверенных открытых ключей из одного элемента.
PIVector<PIByteArray> keyList(const PIByteArray & key) {
return PIVector<PIByteArray>{key};
}
} // namespace
class PIAuthTest: public ::testing::Test {};
//! \~english When both peers already trust each other's signing key the handshake
//! skips the password stage: probe, key exchange, connected.
//! \~russian Когда узлы уже доверяют ключам подписи друг друга, рукопожатие
//! пропускает этап пароля: probe, обмен ключом, connected.
TEST_F(PIAuthTest, AuthorizedKeysFullHandshake) {
PIAuth client(signKey("client-seed"));
PIAuth server(signKey("server-seed"));
bool client_connected = false;
bool server_connected = false;
int client_disconnects = 0;
bool password_requested = false;
PIString server_info;
CONNECTL(&client, connected, [&](PIString) { client_connected = true; });
CONNECTL(&server, connected, [&](PIString info) {
server_connected = true;
server_info = info;
});
CONNECTL(&client, disconnected, [&](PIString) { ++client_disconnects; });
CONNECTL(&client, passwordRequest, [&](PIString * pass) {
password_requested = true;
*pass = "unused";
});
client.setInfoData(PIByteArray::fromAscii("hello-server"));
client.setAuthorizedPublicKeys(keyList(server.getSignPublicKey()));
server.setAuthorizedPublicKeys(keyList(client.getSignPublicKey()));
ASSERT_FALSE(client.getSignPublicKey().isEmpty());
ASSERT_FALSE(server.getSignPublicKey().isEmpty());
client.startClient();
PIByteArray pkt = server.startServer();
ASSERT_FALSE(pkt.isEmpty());
EXPECT_EQ(PIAuth::AuthReply, client.receive(pkt));
EXPECT_EQ(PIAuth::KeyExchange, server.receive(pkt));
EXPECT_EQ(PIAuth::Connected, client.receive(pkt));
EXPECT_EQ(PIAuth::Connected, server.receive(pkt));
EXPECT_TRUE(client_connected);
EXPECT_TRUE(server_connected);
EXPECT_EQ(0, client_disconnects);
EXPECT_FALSE(password_requested);
EXPECT_EQ("hello-server"_a, server_info);
ASSERT_FALSE(client.getSecretKey().isEmpty());
EXPECT_EQ(PICrypt::sizeKey(), client.getSecretKey().size());
EXPECT_EQ(client.getSecretKey(), server.getSecretKey());
}
//! \~english After both peers are Connected an extra round-trip re-sends the
//! application info and keeps the same session key (keep-alive path).
//! \~russian После перехода обоих узлов в Connected дополнительный обмен
//! повторно передает прикладные данные и сохраняет тот же сеансовый ключ
//! (путь keep-alive).
TEST_F(PIAuthTest, ConnectedKeepAliveRoundTrip) {
PIAuth client(signKey("client-seed"));
PIAuth server(signKey("server-seed"));
int client_connected = 0;
int server_connected = 0;
PIString server_info;
client.setInfoData(PIByteArray::fromAscii("kept-info"));
client.setAuthorizedPublicKeys(keyList(server.getSignPublicKey()));
server.setAuthorizedPublicKeys(keyList(client.getSignPublicKey()));
CONNECTL(&client, connected, [&](PIString) { ++client_connected; });
CONNECTL(&server, connected, [&](PIString info) {
++server_connected;
server_info = info;
});
client.startClient();
PIByteArray pkt = server.startServer();
EXPECT_EQ(PIAuth::AuthReply, client.receive(pkt));
EXPECT_EQ(PIAuth::KeyExchange, server.receive(pkt));
EXPECT_EQ(PIAuth::Connected, client.receive(pkt));
EXPECT_EQ(PIAuth::Connected, server.receive(pkt));
// server.receive() left its keep-alive reply in "pkt".
EXPECT_EQ(PIAuth::Connected, client.receive(pkt));
EXPECT_EQ(PIAuth::Connected, server.receive(pkt));
EXPECT_EQ(2, client_connected);
EXPECT_EQ(2, server_connected);
EXPECT_EQ("kept-info"_a, server_info);
EXPECT_EQ(client.getSecretKey(), server.getSecretKey());
}
//! \~english With no mutual trust the client approves the server via the
//! \a authorize event, the server requests a password and both reach Connected
//! with the same session key.
//! \~russian Без взаимного доверия клиент подтверждает сервер через событие
//! \a authorize, сервер запрашивает пароль, и обе стороны доходят до Connected
//! с одинаковым сеансовым ключом.
TEST_F(PIAuthTest, PasswordFullHandshake) {
PIAuth client(signKey("client-seed"));
PIAuth server(signKey("server-seed"));
bool authorized = false;
bool password_asked = false;
bool password_checked = false;
bool password_ok = false;
int client_disconnects = 0;
PIString server_info;
server.setServerPassword("s3cret");
client.setInfoData(PIByteArray::fromAscii("my-info"));
CONNECTL(&client, authorize, [&](PIByteArray, bool * ok) {
authorized = true;
*ok = true;
});
CONNECTL(&client, passwordRequest, [&](PIString * pass) {
password_asked = true;
*pass = "s3cret";
});
CONNECTL(&server, passwordCheck, [&](bool result) {
password_checked = true;
password_ok = result;
});
CONNECTL(&client, disconnected, [&](PIString) { ++client_disconnects; });
CONNECTL(&client, connected, [&](PIString) {});
CONNECTL(&server, connected, [&](PIString info) { server_info = info; });
client.startClient();
PIByteArray pkt = server.startServer();
EXPECT_EQ(PIAuth::AuthReply, client.receive(pkt));
EXPECT_EQ(PIAuth::PassRequest, server.receive(pkt));
EXPECT_EQ(PIAuth::PassRequest, client.receive(pkt));
EXPECT_EQ(PIAuth::KeyExchange, server.receive(pkt));
EXPECT_EQ(PIAuth::Connected, client.receive(pkt));
EXPECT_EQ(PIAuth::Connected, server.receive(pkt));
EXPECT_TRUE(authorized);
EXPECT_TRUE(password_asked);
EXPECT_TRUE(password_checked);
EXPECT_TRUE(password_ok);
EXPECT_EQ(0, client_disconnects);
EXPECT_EQ("my-info"_a, server_info);
ASSERT_FALSE(client.getSecretKey().isEmpty());
EXPECT_EQ(client.getSecretKey(), server.getSecretKey());
}
//! \~english A client that already trusts the server key still has to pass the
//! server password check when the server does not know the client key. The
//! \a authorize event must not fire in that case.
//! \~russian Клиент, уже доверяющий ключу сервера, всё равно проходит проверку
//! пароля, если сервер не знает ключ клиента. Событие \a authorize не должно
//! срабатывать.
TEST_F(PIAuthTest, TrustedServerPasswordStage) {
PIAuth client(signKey("client-seed"));
PIAuth server(signKey("server-seed"));
bool authorized = false;
bool password_asked = false;
server.setServerPassword("pw");
client.setAuthorizedPublicKeys(keyList(server.getSignPublicKey()));
CONNECTL(&client, authorize, [&](PIByteArray, bool * ok) {
authorized = true;
*ok = true;
});
CONNECTL(&client, passwordRequest, [&](PIString * pass) {
password_asked = true;
*pass = "pw";
});
CONNECTL(&server, passwordCheck, [](bool) {});
CONNECTL(&client, connected, [](PIString) {});
CONNECTL(&server, connected, [](PIString) {});
client.startClient();
PIByteArray pkt = server.startServer();
EXPECT_EQ(PIAuth::AuthReply, client.receive(pkt));
EXPECT_EQ(PIAuth::PassRequest, server.receive(pkt));
EXPECT_EQ(PIAuth::PassRequest, client.receive(pkt));
EXPECT_EQ(PIAuth::KeyExchange, server.receive(pkt));
EXPECT_EQ(PIAuth::Connected, client.receive(pkt));
EXPECT_EQ(PIAuth::Connected, server.receive(pkt));
EXPECT_FALSE(authorized);
EXPECT_TRUE(password_asked);
EXPECT_FALSE(client.getSecretKey().isEmpty());
EXPECT_EQ(client.getSecretKey(), server.getSecretKey());
}
//! \~english A wrong password must abort the handshake on the server side and
//! report a failed password check.
//! \~russian Неверный пароль должен прервать рукопожатие на сервере и сообщить
//! о неудачной проверке пароля.
TEST_F(PIAuthTest, WrongPasswordDisconnects) {
PIAuth client(signKey("client-seed"));
PIAuth server(signKey("server-seed"));
bool password_checked = false;
bool password_ok = true;
PIString disconnect_reason;
server.setServerPassword("right");
CONNECTL(&client, authorize, [](PIByteArray, bool * ok) { *ok = true; });
CONNECTL(&client, passwordRequest, [](PIString * pass) { *pass = "wrong"; });
CONNECTL(&server, passwordCheck, [&](bool result) {
password_checked = true;
password_ok = result;
});
CONNECTL(&server, disconnected, [&](PIString reason) { disconnect_reason = reason; });
CONNECTL(&client, connected, [](PIString) {});
CONNECTL(&server, connected, [](PIString) {});
client.startClient();
PIByteArray pkt = server.startServer();
EXPECT_EQ(PIAuth::AuthReply, client.receive(pkt));
EXPECT_EQ(PIAuth::PassRequest, server.receive(pkt));
EXPECT_EQ(PIAuth::PassRequest, client.receive(pkt));
EXPECT_EQ(PIAuth::NotConnected, server.receive(pkt));
EXPECT_TRUE(password_checked);
EXPECT_FALSE(password_ok);
EXPECT_TRUE(disconnect_reason.contains("Invalid password"));
EXPECT_TRUE(server.getSecretKey().isEmpty());
EXPECT_TRUE(client.getSecretKey().isEmpty());
}
//! \~english The server refuses password authentication when no password is
//! configured, even if an empty hash is submitted.
//! \~russian Сервер отказывает в парольной аутентификации, если пароль не задан,
//! даже если передан пустой хеш.
TEST_F(PIAuthTest, EmptyServerPasswordRejects) {
PIAuth client(signKey("client-seed"));
PIAuth server(signKey("server-seed"));
bool password_ok = true;
CONNECTL(&client, authorize, [](PIByteArray, bool * ok) { *ok = true; });
CONNECTL(&client, passwordRequest, [](PIString * pass) { *pass = "anything"; });
CONNECTL(&server, passwordCheck, [&](bool result) { password_ok = result; });
CONNECTL(&client, connected, [](PIString) {});
CONNECTL(&server, connected, [](PIString) {});
client.startClient();
PIByteArray pkt = server.startServer();
EXPECT_EQ(PIAuth::AuthReply, client.receive(pkt));
EXPECT_EQ(PIAuth::PassRequest, server.receive(pkt));
EXPECT_EQ(PIAuth::PassRequest, client.receive(pkt));
EXPECT_EQ(PIAuth::NotConnected, server.receive(pkt));
EXPECT_FALSE(password_ok);
}
//! \~english Rejecting an unknown server in the \a authorize callback aborts the
//! handshake on the client.
//! \~russian Отказ от неизвестного сервера в обработчике \a authorize прерывает
//! рукопожатие на клиенте.
TEST_F(PIAuthTest, AuthorizeRejectedDisconnects) {
PIAuth client(signKey("client-seed"));
PIAuth server(signKey("server-seed"));
bool authorize_called = false;
PIString disconnect_reason;
CONNECTL(&client, authorize, [&](PIByteArray, bool * ok) {
authorize_called = true;
*ok = false;
});
CONNECTL(&client, disconnected, [&](PIString reason) { disconnect_reason = reason; });
CONNECTL(&client, connected, [](PIString) {});
client.startClient();
PIByteArray pkt = server.startServer();
EXPECT_EQ(PIAuth::NotConnected, client.receive(pkt));
EXPECT_TRUE(authorize_called);
EXPECT_TRUE(disconnect_reason.contains("Unauthorised"));
EXPECT_TRUE(client.getSecretKey().isEmpty());
}
//! \~english An empty password from the client callback cancels the handshake.
//! \~russian Пустой пароль из клиентского обработчика отменяет рукопожатие.
TEST_F(PIAuthTest, PasswordRequestCancelledDisconnects) {
PIAuth client(signKey("client-seed"));
PIAuth server(signKey("server-seed"));
PIString disconnect_reason;
server.setServerPassword("pw");
CONNECTL(&client, authorize, [](PIByteArray, bool * ok) { *ok = true; });
CONNECTL(&client, passwordRequest, [](PIString * pass) { pass->clear(); });
CONNECTL(&client, disconnected, [&](PIString reason) { disconnect_reason = reason; });
CONNECTL(&client, connected, [](PIString) {});
CONNECTL(&server, connected, [](PIString) {});
client.startClient();
PIByteArray pkt = server.startServer();
EXPECT_EQ(PIAuth::AuthReply, client.receive(pkt));
EXPECT_EQ(PIAuth::PassRequest, server.receive(pkt));
EXPECT_EQ(PIAuth::NotConnected, client.receive(pkt));
EXPECT_TRUE(disconnect_reason.contains("Canceled by user"));
}
//! \~english A tampered server probe fails signature verification on the client.
//! \~russian Измененный пакет-проба сервера не проходит проверку подписи на клиенте.
TEST_F(PIAuthTest, TamperedProbeRejected) {
PIAuth client(signKey("client-seed"));
PIAuth server(signKey("server-seed"));
PIString disconnect_reason;
CONNECTL(&client, authorize, [](PIByteArray, bool * ok) { *ok = true; });
CONNECTL(&client, disconnected, [&](PIString reason) { disconnect_reason = reason; });
CONNECTL(&client, connected, [](PIString) {});
client.startClient();
PIByteArray pkt = server.startServer();
ASSERT_TRUE(pkt.size() > 0);
pkt[pkt.size() - 1] ^= 0xFF;
EXPECT_EQ(PIAuth::NotConnected, client.receive(pkt));
EXPECT_TRUE(disconnect_reason.contains("Incorrect sign"));
}
//! \~english A too-short packet is rejected without touching the session.
//! \~russian Слишком короткий пакет отклоняется без изменения сессии.
TEST_F(PIAuthTest, InvalidDataSizeRejected) {
PIAuth client(signKey("client-seed"));
PIString disconnect_reason;
CONNECTL(&client, disconnected, [&](PIString reason) { disconnect_reason = reason; });
client.startClient();
PIByteArray empty;
EXPECT_EQ(PIAuth::NotConnected, client.receive(empty));
EXPECT_TRUE(disconnect_reason.contains("invalid data size"));
PIByteArray short_pkt;
short_pkt << (uchar)1 << (uchar)2;
EXPECT_EQ(PIAuth::NotConnected, client.receive(short_pkt));
}
//! \~english A packet whose state does not match the current one is rejected.
//! \~russian Пакет, состояние которого не соответствует текущему, отклоняется.
TEST_F(PIAuthTest, UnexpectedStateRejected) {
PIAuth client(signKey("client-seed"));
PIString disconnect_reason;
CONNECTL(&client, disconnected, [&](PIString reason) { disconnect_reason = reason; });
client.startClient();
PIByteArray pkt;
pkt << (int)PIAuth::PassRequest << PIByteArray::fromAscii("junk");
EXPECT_EQ(PIAuth::NotConnected, client.receive(pkt));
EXPECT_TRUE(disconnect_reason.contains("invalid state"));
}
//! \~english Before the handshake no session key is available; \a stop() clears an
//! established one and the endpoint can handshake again afterwards.
//! \~russian До рукопожатия сеансовый ключ недоступен; \a stop() очищает
//! установленный ключ, после чего конечная точка может пройти рукопожатие снова.
TEST_F(PIAuthTest, SecretKeyAvailableOnlyWhenConnected) {
PIAuth client(signKey("client-seed"));
PIAuth server(signKey("server-seed"));
client.setAuthorizedPublicKeys(keyList(server.getSignPublicKey()));
server.setAuthorizedPublicKeys(keyList(client.getSignPublicKey()));
CONNECTL(&client, connected, [](PIString) {});
CONNECTL(&server, connected, [](PIString) {});
client.startClient();
EXPECT_TRUE(client.getSecretKey().isEmpty());
PIByteArray pkt = server.startServer();
EXPECT_EQ(PIAuth::AuthReply, client.receive(pkt));
EXPECT_TRUE(client.getSecretKey().isEmpty());
EXPECT_EQ(PIAuth::KeyExchange, server.receive(pkt));
EXPECT_EQ(PIAuth::Connected, client.receive(pkt));
ASSERT_FALSE(client.getSecretKey().isEmpty());
client.stop();
EXPECT_TRUE(client.getSecretKey().isEmpty());
// A stopped client can run a fresh handshake with a new server.
PIAuth server2(signKey("server-seed-2"));
client.setAuthorizedPublicKeys(keyList(server2.getSignPublicKey()));
server2.setAuthorizedPublicKeys(keyList(client.getSignPublicKey()));
CONNECTL(&server2, connected, [](PIString) {});
client.startClient();
pkt = server2.startServer();
EXPECT_EQ(PIAuth::AuthReply, client.receive(pkt));
EXPECT_EQ(PIAuth::KeyExchange, server2.receive(pkt));
EXPECT_EQ(PIAuth::Connected, client.receive(pkt));
EXPECT_EQ(PIAuth::Connected, server2.receive(pkt));
ASSERT_FALSE(client.getSecretKey().isEmpty());
EXPECT_EQ(client.getSecretKey(), server2.getSecretKey());
}
//! \~english Two independent handshakes must derive different session keys.
//! \~russian Два независимых рукопожатия должны получить разные сеансовые ключи.
TEST_F(PIAuthTest, SessionKeysAreUnique) {
auto handshake = [](const PIByteArray & client_key, const PIByteArray & server_key) {
PIAuth client(client_key);
PIAuth server(server_key);
client.setAuthorizedPublicKeys(keyList(server.getSignPublicKey()));
server.setAuthorizedPublicKeys(keyList(client.getSignPublicKey()));
CONNECTL(&client, connected, [](PIString) {});
CONNECTL(&server, connected, [](PIString) {});
client.startClient();
PIByteArray pkt = server.startServer();
client.receive(pkt);
server.receive(pkt);
client.receive(pkt);
server.receive(pkt);
return client.getSecretKey();
};
PIByteArray first = handshake(signKey("client-seed"), signKey("server-seed"));
PIByteArray second = handshake(signKey("client-seed"), signKey("server-seed"));
ASSERT_FALSE(first.isEmpty());
ASSERT_FALSE(second.isEmpty());
EXPECT_NE(first, second);
}
//! \~english \a generateSign() is deterministic and the endpoint derives the
//! matching public key from the secret.
//! \~russian \a generateSign() детерминирован, а конечная точка выводит
//! соответствующий открытый ключ из секретного.
TEST_F(PIAuthTest, GenerateSignAndPublicKey) {
PIByteArray sk1 = PIAuth::generateSign(PIByteArray::fromAscii("seed"));
PIByteArray sk2 = PIAuth::generateSign(PIByteArray::fromAscii("seed"));
PIByteArray sk3 = PIAuth::generateSign(PIByteArray::fromAscii("other-seed"));
EXPECT_FALSE(sk1.isEmpty());
EXPECT_EQ(sk1, sk2);
EXPECT_NE(sk1, sk3);
PIAuth endpoint(sk1);
EXPECT_EQ(PICrypt::extractSignPublicKey(sk1), endpoint.getSignPublicKey());
EXPECT_FALSE(endpoint.getSignPublicKey().isEmpty());
}
//! \~english The authorized key list is exposed and replaced through the accessors.
//! \~russian Список доверенных ключей читается и заменяется через аксессоры.
TEST_F(PIAuthTest, AuthorizedKeysAccessors) {
PIAuth endpoint(signKey("seed"));
PIByteArray first = signKey("first");
PIByteArray second = signKey("second");
EXPECT_TRUE(endpoint.getAuthorizedPublicKeys().isEmpty());
endpoint.setAuthorizedPublicKeys(keyList(first));
ASSERT_EQ(1, endpoint.getAuthorizedPublicKeys().size());
EXPECT_EQ(first, endpoint.getAuthorizedPublicKeys()[0]);
endpoint.setAuthorizedPublicKeys(PIVector<PIByteArray>{first, second});
ASSERT_EQ(2, endpoint.getAuthorizedPublicKeys().size());
EXPECT_EQ(first, endpoint.getAuthorizedPublicKeys()[0]);
EXPECT_EQ(second, endpoint.getAuthorizedPublicKeys()[1]);
endpoint.setAuthorizedPublicKeys(PIVector<PIByteArray>());
EXPECT_TRUE(endpoint.getAuthorizedPublicKeys().isEmpty());
}