SHA256
Add channels IT coverage, live UI loading, and runbook
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
package test.it.cases;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import test.it.utils.TestConfig;
|
||||
import test.it.utils.json.JsonBuilders;
|
||||
import test.it.utils.json.JsonParsers;
|
||||
import test.it.utils.log.TestResult;
|
||||
import test.it.utils.ws.WsSession;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
* IT_06_ChannelsApi
|
||||
*
|
||||
* Проверяет базовые happy-path сценарии для новых операций:
|
||||
* - ListSubscriptionsFeed
|
||||
* - GetChannelMessages
|
||||
* - GetMessageThread
|
||||
*/
|
||||
public class IT_06_ChannelsApi {
|
||||
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
public static String run() {
|
||||
TestResult r = new TestResult("IT_06_ChannelsApi");
|
||||
Duration t = Duration.ofSeconds(8);
|
||||
|
||||
final String login = TestConfig.LOGIN();
|
||||
final String bchName = TestConfig.getBlockchainName(login);
|
||||
|
||||
try (WsSession ws = WsSession.open()) {
|
||||
String feedResp = ws.call("ListSubscriptionsFeed", JsonBuilders.listSubscriptionsFeed(login, 200), t);
|
||||
check200(r, feedResp, "ListSubscriptionsFeed");
|
||||
|
||||
int ownSize = JsonParsers.payloadArraySize(feedResp, "ownedChannels");
|
||||
if (ownSize < 0) {
|
||||
r.fail("ListSubscriptionsFeed: отсутствует ownedChannels array, resp=" + feedResp);
|
||||
fail("ownedChannels missing");
|
||||
}
|
||||
r.ok("ListSubscriptionsFeed: ownedChannels size=" + ownSize);
|
||||
|
||||
String chResp = ws.call("GetChannelMessages", JsonBuilders.getChannelMessages(bchName, 0, "", 200, "asc"), t);
|
||||
check200(r, chResp, "GetChannelMessages");
|
||||
|
||||
JsonNode chRoot = MAPPER.readTree(chResp);
|
||||
JsonNode messages = chRoot.path("payload").path("messages");
|
||||
if (!messages.isArray()) {
|
||||
r.fail("GetChannelMessages: payload.messages не массив, resp=" + chResp);
|
||||
fail("messages is not array");
|
||||
}
|
||||
r.ok("GetChannelMessages: messages size=" + messages.size());
|
||||
|
||||
if (messages.size() > 0) {
|
||||
JsonNode first = messages.get(0);
|
||||
int blockNumber = first.path("messageRef").path("blockNumber").asInt(-1);
|
||||
String blockHash = first.path("messageRef").path("blockHash").asText("");
|
||||
|
||||
if (blockNumber > 0 && !blockHash.isBlank()) {
|
||||
String threadResp = ws.call(
|
||||
"GetMessageThread",
|
||||
JsonBuilders.getMessageThread(bchName, blockNumber, blockHash, 20, 2, 50),
|
||||
t
|
||||
);
|
||||
check200(r, threadResp, "GetMessageThread");
|
||||
|
||||
JsonNode threadRoot = MAPPER.readTree(threadResp).path("payload");
|
||||
if (!threadRoot.path("ancestors").isArray() || !threadRoot.has("focus") || !threadRoot.path("descendants").isArray()) {
|
||||
r.fail("GetMessageThread: неверная форма payload, resp=" + threadResp);
|
||||
fail("thread payload shape invalid");
|
||||
}
|
||||
r.ok("GetMessageThread: payload shape OK");
|
||||
} else {
|
||||
r.ok("GetMessageThread: пропущено, у первого сообщения нет корректного ref");
|
||||
}
|
||||
} else {
|
||||
r.ok("GetMessageThread: пропущено, в канале нет сообщений");
|
||||
}
|
||||
|
||||
} catch (Throwable e) {
|
||||
r.fail("IT_06_ChannelsApi упал: " + e.getMessage());
|
||||
}
|
||||
|
||||
return r.summaryLine();
|
||||
}
|
||||
|
||||
private static void check200(TestResult r, String resp, String op) {
|
||||
int st = JsonParsers.status(resp);
|
||||
if (st != 200) {
|
||||
r.fail(op + ": ожидали status=200, получили " + st + ", resp=" + resp);
|
||||
fail(op + " status=" + st);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import test.it.cases.IT_02_Sessions;
|
||||
import test.it.cases.IT_03_AddBlock_NoAuth;
|
||||
import test.it.cases.IT_04_UserParams_NoAuth;
|
||||
import test.it.cases.IT_05_UserConnections;
|
||||
import test.it.cases.IT_06_ChannelsApi;
|
||||
import test.it.utils.log.TestLog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -56,6 +57,9 @@ public class IT_RunAllMain {
|
||||
String s5 = IT_05_UserConnections.run(); summaries.add(s5);
|
||||
if (s5.contains("FAIL:")) { failed++; if (STOP_ON_FIRST_FAIL) return finishEarly(summaries, failed); }
|
||||
|
||||
String s6 = IT_06_ChannelsApi.run(); summaries.add(s6);
|
||||
if (s6.contains("FAIL:")) { failed++; if (STOP_ON_FIRST_FAIL) return finishEarly(summaries, failed); }
|
||||
|
||||
return finish(summaries, failed);
|
||||
}
|
||||
|
||||
|
||||
@@ -255,6 +255,61 @@ public final class JsonBuilders {
|
||||
""".formatted(requestId, login);
|
||||
}
|
||||
|
||||
public static String listSubscriptionsFeed(String login, int limit) {
|
||||
String requestId = TestIds.next("subsfeed");
|
||||
return """
|
||||
{
|
||||
"op": "ListSubscriptionsFeed",
|
||||
"requestId": "%s",
|
||||
"payload": {
|
||||
"login": "%s",
|
||||
"limit": %d
|
||||
}
|
||||
}
|
||||
""".formatted(requestId, login, limit);
|
||||
}
|
||||
|
||||
public static String getChannelMessages(String ownerBlockchainName, int channelRootBlockNumber, String channelRootBlockHash, int limit, String sort) {
|
||||
String requestId = TestIds.next("chmsg");
|
||||
String hash = channelRootBlockHash == null ? "" : channelRootBlockHash;
|
||||
return """
|
||||
{
|
||||
"op": "GetChannelMessages",
|
||||
"requestId": "%s",
|
||||
"payload": {
|
||||
"channel": {
|
||||
"ownerBlockchainName": "%s",
|
||||
"channelRootBlockNumber": %d,
|
||||
"channelRootBlockHash": "%s"
|
||||
},
|
||||
"limit": %d,
|
||||
"sort": "%s"
|
||||
}
|
||||
}
|
||||
""".formatted(requestId, ownerBlockchainName, channelRootBlockNumber, hash, limit, sort == null ? "asc" : sort);
|
||||
}
|
||||
|
||||
public static String getMessageThread(String blockchainName, int blockNumber, String blockHash, int depthUp, int depthDown, int limitChildrenPerNode) {
|
||||
String requestId = TestIds.next("thread");
|
||||
String hash = blockHash == null ? "" : blockHash;
|
||||
return """
|
||||
{
|
||||
"op": "GetMessageThread",
|
||||
"requestId": "%s",
|
||||
"payload": {
|
||||
"message": {
|
||||
"blockchainName": "%s",
|
||||
"blockNumber": %d,
|
||||
"blockHash": "%s"
|
||||
},
|
||||
"depthUp": %d,
|
||||
"depthDown": %d,
|
||||
"limitChildrenPerNode": %d
|
||||
}
|
||||
}
|
||||
""".formatted(requestId, blockchainName, blockNumber, hash, depthUp, depthDown, limitChildrenPerNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Подпись CreateAuthSession(v2):
|
||||
* preimage = "AUTH_CREATE_SESSION:" + login + ":" + sessionKey + ":" + storagePwd + ":" + timeMs + ":" + authNonce
|
||||
|
||||
@@ -287,4 +287,32 @@ public final class JsonParsers {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static int payloadArraySize(String json, String field) {
|
||||
try {
|
||||
JsonNode root = MAPPER.readTree(json);
|
||||
JsonNode payload = root.get("payload");
|
||||
if (payload == null) return -1;
|
||||
JsonNode arr = payload.get(field);
|
||||
if (arr == null || !arr.isArray()) return -1;
|
||||
return arr.size();
|
||||
} catch (Exception e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public static int payloadNestedArraySize(String json, String objectField, String arrayField) {
|
||||
try {
|
||||
JsonNode root = MAPPER.readTree(json);
|
||||
JsonNode payload = root.get("payload");
|
||||
if (payload == null) return -1;
|
||||
JsonNode obj = payload.get(objectField);
|
||||
if (obj == null || !obj.isObject()) return -1;
|
||||
JsonNode arr = obj.get(arrayField);
|
||||
if (arr == null || !arr.isArray()) return -1;
|
||||
return arr.size();
|
||||
} catch (Exception e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user