Skip to content

Commit bf8c267

Browse files
authored
Merge branch 'main' into feat/tck-create-ethereum-transaction
2 parents 96984ef + e8e7ba2 commit bf8c267

65 files changed

Lines changed: 1384 additions & 598 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/scripts/__tests__/jest/issue-assign.test.js

Lines changed: 71 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ jest.mock('../../shared/api/github-api', () => ({
1313
assignIssue: jest.fn(),
1414
}));
1515

16+
jest.mock('../../shared/helpers/coderabbit-plan.js', () => ({
17+
triggerCodeRabbitPlan: jest.fn(),
18+
hasExistingCodeRabbitPlan: jest.fn(),
19+
}));
20+
1621
jest.mock('../../shared/helpers/comment', () => ({
1722
buildAlreadyAssignedComment: jest.fn(() => 'already assigned'),
1823
buildGuardComment: jest.fn(() => 'guard comment'),
@@ -35,6 +40,11 @@ const { runAssignmentFlow } = require('../../shared/core/issue-assign');
3540
const githubApi = require('../../shared/api/github-api');
3641
const spam = require('../../shared/helpers/spam');
3742

43+
const {
44+
triggerCodeRabbitPlan,
45+
hasExistingCodeRabbitPlan,
46+
} = require('../../shared/helpers/coderabbit-plan.js');
47+
3848
function createContext(overrides = {}) {
3949
return {
4050
payload: {
@@ -86,6 +96,9 @@ beforeEach(() => {
8696
spam.isSpamBlockedLevel.mockReturnValue(false);
8797
spam.isSpamLimited.mockReturnValue(false);
8898
spam.getAssignmentLimit.mockReturnValue(5);
99+
100+
hasExistingCodeRabbitPlan.mockResolvedValue(false);
101+
triggerCodeRabbitPlan.mockResolvedValue(true);
89102
});
90103

91104
describe('runAssignmentFlow - validation', () => {
@@ -597,8 +610,9 @@ describe('runAssignmentFlow - assignment', () => {
597610
expect(githubApi.assignIssue).not.toHaveBeenCalled();
598611
});
599612

600-
test('assigns issue when all checks pass', async () => {
613+
test('assigns issue and triggers CodeRabbit when all checks pass', async () => {
601614
githubApi.getOpenAssignments.mockResolvedValue(0);
615+
hasExistingCodeRabbitPlan.mockResolvedValue(false);
602616

603617
const github = createGithub();
604618
const context = createContext();
@@ -613,8 +627,54 @@ describe('runAssignmentFlow - assignment', () => {
613627
username: 'parv',
614628
});
615629

630+
expect(hasExistingCodeRabbitPlan).toHaveBeenCalledWith(
631+
github,
632+
'hiero-ledger',
633+
'hiero-sdk-python',
634+
10
635+
);
636+
637+
expect(triggerCodeRabbitPlan).toHaveBeenCalledWith(
638+
github,
639+
'hiero-ledger',
640+
'hiero-sdk-python',
641+
context.payload.issue
642+
);
643+
616644
expect(githubApi.postIssueComment).not.toHaveBeenCalled();
617645
});
646+
647+
test('does not trigger CodeRabbit when a plan already exists', async () => {
648+
githubApi.getOpenAssignments.mockResolvedValue(0);
649+
hasExistingCodeRabbitPlan.mockResolvedValue(true);
650+
651+
const github = createGithub();
652+
const context = createContext();
653+
654+
await runAssignmentFlow({ github, context });
655+
656+
expect(githubApi.assignIssue).toHaveBeenCalled();
657+
expect(hasExistingCodeRabbitPlan).toHaveBeenCalled();
658+
expect(triggerCodeRabbitPlan).not.toHaveBeenCalled();
659+
});
660+
661+
test('does not fail assignment when CodeRabbit trigger fails', async () => {
662+
githubApi.getOpenAssignments.mockResolvedValue(0);
663+
hasExistingCodeRabbitPlan.mockResolvedValue(false);
664+
triggerCodeRabbitPlan.mockRejectedValue(
665+
new Error('CodeRabbit API failed')
666+
);
667+
668+
const github = createGithub();
669+
const context = createContext();
670+
671+
await expect(
672+
runAssignmentFlow({ github, context })
673+
).resolves.not.toThrow();
674+
675+
expect(githubApi.assignIssue).toHaveBeenCalled();
676+
expect(triggerCodeRabbitPlan).toHaveBeenCalled();
677+
});
618678
});
619679

620680
describe('runAssignmentFlow - error handling', () => {
@@ -676,31 +736,21 @@ describe('runAssignmentFlow - error handling', () => {
676736
expect(githubApi.assignIssue).toHaveBeenCalled();
677737
});
678738

679-
test('propagates post comment errors', async () => {
680-
githubApi.postIssueComment.mockRejectedValue(
681-
new Error('Comment failed')
739+
test('does not trigger CodeRabbit when assignment fails', async () => {
740+
githubApi.assignIssue.mockRejectedValue(
741+
new Error('Assignment failed')
682742
);
683743

684-
githubApi.countCompletedIssuesWithLabel.mockResolvedValue(0);
685-
686744
const github = createGithub();
745+
const context = createContext();
687746

688-
const context = createContext({
689-
issue: {
690-
number: 42,
691-
assignees: [],
692-
labels: [
693-
{
694-
name: 'skill: intermediate',
695-
},
696-
],
697-
},
747+
await runAssignmentFlow({
748+
github,
749+
context,
698750
});
699751

700-
await expect(
701-
runAssignmentFlow({ github, context })
702-
).rejects.toThrow('Comment failed');
703-
704-
expect(githubApi.assignIssue).not.toHaveBeenCalled();
752+
expect(githubApi.assignIssue).toHaveBeenCalled();
753+
expect(hasExistingCodeRabbitPlan).not.toHaveBeenCalled();
754+
expect(triggerCodeRabbitPlan).not.toHaveBeenCalled();
705755
});
706756
});

.github/scripts/shared/core/issue-assign.js

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ const {
5252
getAssignmentLimit,
5353
} = require('../helpers/spam.js');
5454

55+
const {
56+
triggerCodeRabbitPlan,
57+
hasExistingCodeRabbitPlan,
58+
} = require('../helpers/coderabbit-plan.js');
59+
5560
/**
5661
* Returns true if a comment contains the `/assign` command.
5762
*
@@ -281,14 +286,46 @@ async function runAssignmentFlow({ github, context }) {
281286
username: commenter,
282287
});
283288
} catch (error) {
284-
console.error("[assign-bot] Failed to assign issue:", {
289+
console.error('[assign-bot] Failed to assign issue:', {
285290
message: error.message,
286291
});
292+
return;
287293
}
288-
return;
289294

290-
}
295+
// Trigger CodeRabbit after successful assignment
296+
try {
297+
const planExists = await hasExistingCodeRabbitPlan(
298+
github,
299+
owner,
300+
repoName,
301+
issueNumber
302+
);
291303

304+
if (planExists === false) {
305+
await triggerCodeRabbitPlan(
306+
github,
307+
owner,
308+
repoName,
309+
issue
310+
);
311+
} else if (planExists === true) {
312+
console.log(
313+
`[assign-bot] CodeRabbit plan already exists for #${issueNumber}`
314+
);
315+
} else {
316+
console.log(
317+
`[assign-bot] Unable to determine whether a CodeRabbit plan exists for #${issueNumber}. Skipping trigger.`
318+
);
319+
}
320+
} catch (error) {
321+
console.error('[assign-bot] CodeRabbit plan trigger failed:', {
322+
message: error.message,
323+
issueNumber,
324+
});
325+
}
326+
327+
return;
328+
}
292329
module.exports = {
293330
runAssignmentFlow,
294331
};

.github/scripts/coderabbit_plan_trigger.js renamed to .github/scripts/shared/helpers/coderabbit-plan.js

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Script to trigger CodeRabbit plan for all difficulty-labeled issues (including GFI)
22

33
const CODERABBIT_MARKER = '<!-- CodeRabbit Plan Trigger -->';
4-
const { DIFFICULTY_LABELS } = require('./shared/labels.js');
4+
const { DIFFICULTY_LABELS } = require('../labels.js');
55

66
async function triggerCodeRabbitPlan(github, owner, repo, issue, marker = CODERABBIT_MARKER) {
77
const comment = `${marker} @coderabbitai plan`;
@@ -73,7 +73,7 @@ async function hasExistingCodeRabbitPlan(github, owner, repo, issueNumber) {
7373
issueNumber,
7474
});
7575
// Return false to allow plan trigger attempt (fail-open for better UX)
76-
return false;
76+
return null;
7777
}
7878
}
7979

@@ -110,10 +110,18 @@ function logSummary(owner, repo, issue) {
110110
}
111111

112112
// Main workflow handler (default export for workflow usage)
113-
async function main({ github, context }) {
113+
async function triggerCodeRabbitPlanForIssue({ github, context }) {
114114
try {
115115
const { owner, repo } = context.repo;
116-
const { issue: eventIssue, label } = context.payload;
116+
117+
const payload = context?.payload;
118+
119+
if (!payload) {
120+
console.log('No event payload');
121+
return;
122+
}
123+
124+
const { issue: eventIssue, label } = payload;
117125

118126
// Validations
119127
if (!eventIssue?.number) return console.log('No issue in payload');
@@ -149,10 +157,9 @@ async function main({ github, context }) {
149157
}
150158
}
151159

152-
// Default export for workflow usage: await script({ github, context })
153-
module.exports = main;
154-
155-
// Named exports for reuse by other scripts (e.g., GFI assignment bot)
156-
module.exports.triggerCodeRabbitPlan = triggerCodeRabbitPlan;
157-
module.exports.hasExistingCodeRabbitPlan = hasExistingCodeRabbitPlan;
158-
module.exports.CODERABBIT_MARKER = CODERABBIT_MARKER;
160+
module.exports = {
161+
triggerCodeRabbitPlan,
162+
hasExistingCodeRabbitPlan,
163+
CODERABBIT_MARKER,
164+
triggerCodeRabbitPlanForIssue,
165+
};

.github/workflows/approved-issues.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,5 @@ jobs:
6363
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
6464
with:
6565
script: |
66-
const script = require('./.github/scripts/coderabbit_plan_trigger.js');
67-
await script({ github, context });
66+
const { triggerCodeRabbitPlanForIssue } = require('./.github/scripts/shared/helpers/coderabbit-plan.js');
67+
await triggerCodeRabbitPlanForIssue({ github, context });

src/hiero_sdk_python/consensus/topic_message_submit_transaction.py

Lines changed: 28 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44

55
from hiero_sdk_python.channels import _Channel
66
from hiero_sdk_python.consensus.topic_id import TopicId
7-
from hiero_sdk_python.crypto.private_key import PrivateKey
87
from hiero_sdk_python.executable import _Method
98
from hiero_sdk_python.hapi.services import consensus_submit_message_pb2, transaction_pb2
109
from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import (
1110
SchedulableTransactionBody,
1211
)
12+
from hiero_sdk_python.schedule.schedule_create_transaction import ScheduleCreateTransaction
1313
from hiero_sdk_python.transaction.chunked_transaction import ChunkedTransaction
1414
from hiero_sdk_python.transaction.custom_fee_limit import CustomFeeLimit
1515

@@ -107,33 +107,6 @@ def set_message(self, message: bytes | str) -> TopicMessageSubmitTransaction:
107107
self._total_chunks = self.get_required_chunks()
108108
return self
109109

110-
def set_chunk_size(self, chunk_size: int) -> TopicMessageSubmitTransaction:
111-
"""
112-
Set maximum chunk size in bytes.
113-
114-
Args:
115-
chunk_size (int): The size of each chunk in bytes.
116-
117-
Returns:
118-
TopicMessageSubmitTransaction: This transaction instance (for chaining).
119-
"""
120-
super().set_chunk_size(chunk_size)
121-
self._total_chunks = self.get_required_chunks()
122-
return self
123-
124-
def set_max_chunks(self, max_chunks: int) -> TopicMessageSubmitTransaction:
125-
"""
126-
Set maximum allowed chunks.
127-
128-
Args:
129-
max_chunks (int): The maximum number of chunks allowed.
130-
131-
Returns:
132-
TopicMessageSubmitTransaction: This transaction instance (for chaining).
133-
"""
134-
super().set_max_chunks(max_chunks)
135-
return self
136-
137110
def set_custom_fee_limits(self, custom_fee_limits: list[CustomFeeLimit]) -> TopicMessageSubmitTransaction:
138111
"""
139112
Sets the maximum custom fees that the user is willing to pay for the message.
@@ -186,27 +159,26 @@ def _build_proto_body(self) -> consensus_submit_message_pb2.ConsensusSubmitMessa
186159
if not self.message:
187160
raise ValueError("Missing required fields: message.")
188161

189-
content = self._message_as_bytes()
162+
contents = self._message_as_bytes()
190163

191-
start_index = self._current_chunk_index * self.chunk_size
192-
end_index = min(start_index + self.chunk_size, len(content))
193-
chunk_content = content[start_index:end_index]
164+
if self._total_chunks > 1 and self._current_chunk_index is not None:
165+
chunk_info = consensus_submit_message_pb2.ConsensusMessageChunkInfo(
166+
initialTransactionID=self._initial_transaction_id._to_proto(),
167+
total=self._total_chunks,
168+
number=self._current_chunk_index + 1,
169+
)
194170

195-
body = consensus_submit_message_pb2.ConsensusSubmitMessageTransactionBody(
196-
topicID=self.topic_id._to_proto() if self.topic_id else None, message=chunk_content
197-
)
171+
chunk_content = self._current_chunk_slice(contents)
198172

199-
# Multi-chunk metadata
200-
if self._total_chunks > 1:
201-
body.chunkInfo.CopyFrom(
202-
consensus_submit_message_pb2.ConsensusMessageChunkInfo(
203-
initialTransactionID=self._initial_transaction_id._to_proto(),
204-
total=self._total_chunks,
205-
number=self._current_chunk_index + 1,
206-
)
173+
return consensus_submit_message_pb2.ConsensusSubmitMessageTransactionBody(
174+
topicID=self.topic_id._to_proto() if self.topic_id else None,
175+
message=chunk_content,
176+
chunkInfo=chunk_info,
207177
)
208178

209-
return body
179+
return consensus_submit_message_pb2.ConsensusSubmitMessageTransactionBody(
180+
topicID=self.topic_id._to_proto() if self.topic_id else None, message=contents
181+
)
210182

211183
def build_transaction_body(self) -> transaction_pb2.TransactionBody:
212184
"""
@@ -220,6 +192,18 @@ def build_transaction_body(self) -> transaction_pb2.TransactionBody:
220192
transaction_body.consensusSubmitMessage.CopyFrom(consensus_submit_message_body)
221193
return transaction_body
222194

195+
def schedule(self) -> ScheduleCreateTransaction:
196+
"""
197+
Converts this transaction into a scheduled transaction.
198+
"""
199+
if self.message is not None and len(self._message_as_bytes()) > self.chunk_size:
200+
raise RuntimeError(
201+
f"Cannot schedule TopicMessageSubmitTransaction because the message exceeds "
202+
f"the maximum chunk size of {self.chunk_size} bytes"
203+
)
204+
205+
return super().schedule()
206+
223207
def build_scheduled_body(self) -> SchedulableTransactionBody:
224208
"""
225209
Builds the scheduled transaction body for this topic message submit transaction.
@@ -243,16 +227,3 @@ def _get_method(self, channel: _Channel) -> _Method:
243227
_Method: The method object with bound transaction execution.
244228
"""
245229
return _Method(transaction_func=channel.topic.submitMessage, query_func=None)
246-
247-
def sign(self, private_key: PrivateKey) -> TopicMessageSubmitTransaction:
248-
"""
249-
Signs the transaction using the provided private key.
250-
251-
Args:
252-
private_key (PrivateKey): The private key to sign the transaction with.
253-
254-
Returns:
255-
TopicMessageSubmitTransaction: This transaction instance (for chaining).
256-
"""
257-
super().sign(private_key)
258-
return self

0 commit comments

Comments
 (0)