diff --git a/src/test/java/test/it/cases/IT_03_AddBlock_NoAuth.java b/src/test/java/test/it/cases/IT_03_AddBlock_NoAuth.java index 7912597..b7d4945 100644 --- a/src/test/java/test/it/cases/IT_03_AddBlock_NoAuth.java +++ b/src/test/java/test/it/cases/IT_03_AddBlock_NoAuth.java @@ -1,7 +1,10 @@ package test.it.cases; import blockchain.MsgSubType; -import blockchain.body.*; +import blockchain.body.ConnectionBody; +import blockchain.body.CreateChannelBody; +import blockchain.body.HeaderBody; +import blockchain.body.TextBody; import test.it.blockchain.AddBlockSender; import test.it.blockchain.ChainState; import test.it.utils.TestConfig; @@ -14,17 +17,24 @@ import java.time.Duration; import static org.junit.jupiter.api.Assertions.*; /** - * IT_03_AddBlock_NoAuth — сценарий блоков (новый формат + каналы). + * IT_03_AddBlock_NoAuth — сценарий блоков (новый формат + каналы + связи). * - * ВАЖНО: - * - TECH: Header + CreateChannel идут по тех-линии (hasLine у CreateChannel). - * - TEXT: посты в каналах — отдельные линии, root = Header(канал "0") или CreateChannel(канал "X"). - * - REPLY (subType=20): без линии, target может указывать на чужой блокчейн, и ОБЯЗАТЕЛЬНО содержит toBlockNumber+toBlockHash32. + * CONNECTION (type=3): + * - всегда имеет hasLine (lineCode+prevLineNumber+prevLineHash32+thisLineNumber) + * - всегда имеет target: + * toBlockchainName + toBlockGlobalNumber + toBlockHash32 + * + * Правило target для связей/подписок: + * - FRIEND/CONTACT -> target = HEADER цели (blockNumber=0) + * - FOLLOW пользователя -> target = HEADER цели (blockNumber=0) + * - FOLLOW канала -> target = ROOT канала: + * канал "0" -> HEADER (0) + * канал "X" -> CREATE_CHANNEL (blockNumber create_channel) */ public class IT_03_AddBlock_NoAuth { public static void main(String[] args) { - TestLog.info("Standalone: этот тест требует заранее созданных пользователей -> сначала запускаю IT_01_AddUser"); + TestLog.info("Standalone: этот тест требует заранее созданных пользователей -> запускаю IT_01_AddUser"); System.out.println(IT_01_AddUser.run()); String summary = run(); System.out.println(summary); @@ -50,7 +60,8 @@ public class IT_03_AddBlock_NoAuth { "IT_03:\n" + " USER1=" + u1 + " bch=" + bch1 + "\n" + " USER2=" + u2 + " bch=" + bch2 + "\n" + - " USER3=" + u3 + " bch=" + bch3 + " USER3=" + u3 + " bch=" + bch3 + "\n" + + "\nСценарий: каналы + кросс-чейн reply + connections (follow/friend/contact/uncontact)." ); } @@ -63,15 +74,19 @@ public class IT_03_AddBlock_NoAuth { sender1.send(new HeaderBody(u1), t); assertTrue(st1.hasHeader()); - // канал "0" (root=HEADER) — по умолчанию существует - int root0 = st1.rootChannel0(); // lineCode для канала "0" = 0 + int u1HeaderBlock = 0; + byte[] u1HeaderHash = st1.getHash32(u1HeaderBlock); + assertNotNull(u1HeaderHash); + + // канал "0" root = HEADER (0) + int root0 = st1.rootChannel0(); // POST в канал "0" { var ln = st1.nextTextLineByRoot(root0); sender1.send(new TextBody( MsgSubType.TEXT_POST, - root0, // lineCode + root0, ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber, "U1: story/post in channel 0", null, null, null @@ -88,16 +103,15 @@ public class IT_03_AddBlock_NoAuth { { var ln = st1.nextLineByType(ChainState.TYPE_TECH); sender1.send(new CreateChannelBody( - 0, // lineCode для TECH линии + 0, // lineCode TECH ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber, "News" ), t); - newsRootBlock = st1.lastBlockNumber(); // root канала = blockNumber CREATE_CHANNEL + newsRootBlock = st1.lastBlockNumber(); // root канала = blockNumber этого CREATE_CHANNEL newsRootHash = st1.getHash32(newsRootBlock); assertNotNull(newsRootHash); - // зарегистрируем root канала для тестового state, чтобы nextTextLineByRoot() работал st1.registerTextChannelRoot(newsRootBlock, newsRootHash); } @@ -108,7 +122,7 @@ public class IT_03_AddBlock_NoAuth { var ln = st1.nextTextLineByRoot(newsRootBlock); sender1.send(new TextBody( MsgSubType.TEXT_POST, - newsRootBlock, // lineCode = root блока канала (CREATE_CHANNEL) + newsRootBlock, ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber, "U1: News post #0", null, null, null @@ -124,19 +138,19 @@ public class IT_03_AddBlock_NoAuth { var ln = st1.nextTextLineByRoot(newsRootBlock); sender1.send(new TextBody( MsgSubType.TEXT_POST, - newsRootBlock, // lineCode + newsRootBlock, ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber, "U1: News post #1", null, null, null ), t); } - // EDIT_POST (является частью линии; lineCode обязателен) + // EDIT_POST (в линии канала) -> target на ОРИГИНАЛЬНЫЙ POST (без toBlockchainName) { var ln = st1.nextTextLineByRoot(newsRootBlock); sender1.send(new TextBody( MsgSubType.TEXT_EDIT_POST, - newsRootBlock, // lineCode + newsRootBlock, ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber, "U1: News post #0 (EDIT)", null, @@ -146,7 +160,7 @@ public class IT_03_AddBlock_NoAuth { } // ========================= - // USER2 (ответ в чужой канал) + // USER2 // ========================= ChainState st2 = new ChainState(); AddBlockSender sender2 = new AddBlockSender(ws, st2, u2, bch2, TestConfig.getBlockchainPrivatKey(u2)); @@ -154,8 +168,45 @@ public class IT_03_AddBlock_NoAuth { sender2.send(new HeaderBody(u2), t); assertTrue(st2.hasHeader()); - // REPLY (20): ответ на post в чужом блокчейне/канале - // ВАЖНО: REPLY не имеет line-полей вообще, поэтому используем фабрику newReply(). + int u2HeaderBlock = 0; + byte[] u2HeaderHash = st2.getHash32(u2HeaderBlock); + assertNotNull(u2HeaderHash); + + // ========================= + // СВЯЗИ (CONNECTION) + // ========================= + + // 1) U1 подписался на U2 (FOLLOW на пользователя -> target=HEADER U2) + sendConnection(sender1, st1, MsgSubType.CONNECTION_FOLLOW, + bch2, u2HeaderBlock, u2HeaderHash, + "U1 follows U2 (target=U2 HEADER)", t); + + // 2) U2 подписался на канал U1 "News" (FOLLOW на канал -> target=root CREATE_CHANNEL U1) + sendConnection(sender2, st2, MsgSubType.CONNECTION_FOLLOW, + bch1, newsRootBlock, newsRootHash, + "U2 follows U1 channel 'News' (target=U1 CREATE_CHANNEL root)", t); + + // 3) FRIEND взаимно (на HEADER) + sendConnection(sender1, st1, MsgSubType.CONNECTION_FRIEND, + bch2, u2HeaderBlock, u2HeaderHash, + "U1 -> U2: FRIEND", t); + + sendConnection(sender2, st2, MsgSubType.CONNECTION_FRIEND, + bch1, u1HeaderBlock, u1HeaderHash, + "U2 -> U1: FRIEND", t); + + // 4) CONTACT несколько + sendConnection(sender1, st1, MsgSubType.CONNECTION_CONTACT, + bch2, u2HeaderBlock, u2HeaderHash, + "U1 -> U2: CONTACT", t); + + sendConnection(sender2, st2, MsgSubType.CONNECTION_CONTACT, + bch1, u1HeaderBlock, u1HeaderHash, + "U2 -> U1: CONTACT", t); + + // ========================= + // USER2 REPLY (ответ в чужой канал) + // ========================= { sender2.send(TextBody.newReply( bch1, @@ -166,7 +217,7 @@ public class IT_03_AddBlock_NoAuth { } // ========================= - // USER3 (просто чтобы оставалось как раньше) + // USER3 + доп. контакт // ========================= ChainState st3 = new ChainState(); AddBlockSender sender3 = new AddBlockSender(ws, st3, u3, bch3, TestConfig.getBlockchainPrivatKey(u3)); @@ -174,7 +225,21 @@ public class IT_03_AddBlock_NoAuth { sender3.send(new HeaderBody(u3), t); assertTrue(st3.hasHeader()); - r.ok("IT_03 сценарий блоков выполнен"); + int u3HeaderBlock = 0; + byte[] u3HeaderHash = st3.getHash32(u3HeaderBlock); + assertNotNull(u3HeaderHash); + + // U1 -> U3: CONTACT + sendConnection(sender1, st1, MsgSubType.CONNECTION_CONTACT, + bch3, u3HeaderBlock, u3HeaderHash, + "U1 -> U3: CONTACT", t); + + // 5) U1 убирает U2 из контактов (UNCONTACT) + sendConnection(sender1, st1, MsgSubType.CONNECTION_UNCONTACT, + bch2, u2HeaderBlock, u2HeaderHash, + "U1 -> U2: UNCONTACT", t); + + r.ok("IT_03 сценарий блоков + connections выполнен"); } catch (Throwable e) { r.fail("IT_03 упал: " + e.getMessage()); @@ -182,4 +247,43 @@ public class IT_03_AddBlock_NoAuth { return r.summaryLine(); } + + /** + * Отправка 1 блока CONNECTION. + * + * ВАЖНО: ConnectionBody НЕ содержит note в байтах. + * Если нужно “описание” — логируем отдельно. + */ + private static void sendConnection(AddBlockSender sender, + ChainState st, + short subType, + String toBlockchainName, + int toBlockNumber, + byte[] toBlockHash32, + String logNote, + Duration timeout) { + + if (TestConfig.DEBUG()) { + TestLog.info("CONNECTION: subType=" + (subType & 0xFFFF) + + " to=" + toBlockchainName + + " targetBlock=" + toBlockNumber + + " note=" + logNote); + } + + var ln = st.nextLineByType(ChainState.TYPE_CONNECTION); + + // КОНСТРУКТОР ИЗ ТВОЕГО КОДА: + // ConnectionBody(int lineCode, int prevLineNumber, byte[] prevLineHash32, int thisLineNumber, + // short subType, String toBlockchainName, int toBlockGlobalNumber, byte[] toBlockHash32) + sender.send(new ConnectionBody( + 0, // lineCode для connection линии + ln.prevLineNumber, + ln.prevLineHash32, + ln.thisLineNumber, + subType, + toBlockchainName, + toBlockNumber, + toBlockHash32 + ), timeout); + } } \ No newline at end of file