@@ -1165,4 +1165,116 @@ describe("sendMessageIMessage receipts", () => {
11651165
11661166 expect ( runCliJson ) . not . toHaveBeenCalled ( ) ;
11671167 } ) ;
1168+
1169+ describe ( "actions.reply gating" , ( ) => {
1170+ const BASE_CFG = {
1171+ channels : {
1172+ imessage : {
1173+ accounts : { default : { } } ,
1174+ } ,
1175+ } ,
1176+ } ;
1177+ const REPLY_CFG = {
1178+ channels : {
1179+ imessage : {
1180+ accounts : {
1181+ default : { actions : { reply : true } } ,
1182+ } ,
1183+ } ,
1184+ } ,
1185+ } ;
1186+ const NO_REPLY_CFG = {
1187+ channels : {
1188+ imessage : {
1189+ accounts : {
1190+ default : { actions : { reply : false } } ,
1191+ } ,
1192+ } ,
1193+ } ,
1194+ } ;
1195+
1196+ it ( "includes reply_to when actions.reply is true" , async ( ) => {
1197+ const client = createClient ( { guid : "p:0/GUID" , status : "sent" } ) ;
1198+
1199+ await sendMessageIMessage ( "+15551234567" , "hello" , {
1200+ config : REPLY_CFG ,
1201+ client,
1202+ replyToId : "reply-guid" ,
1203+ } ) ;
1204+
1205+ const params = ( client . request as ReturnType < typeof vi . fn > ) . mock . calls [ 0 ] [ 1 ] ;
1206+ expect ( params . reply_to ) . toBe ( "reply-guid" ) ;
1207+ } ) ;
1208+
1209+ it ( "includes reply_to when actions.reply is not set (default-enabled)" , async ( ) => {
1210+ const client = createClient ( { guid : "p:0/GUID" , status : "sent" } ) ;
1211+
1212+ await sendMessageIMessage ( "+15551234567" , "hello" , {
1213+ config : BASE_CFG ,
1214+ client,
1215+ replyToId : "reply-guid" ,
1216+ } ) ;
1217+
1218+ const params = ( client . request as ReturnType < typeof vi . fn > ) . mock . calls [ 0 ] [ 1 ] ;
1219+ expect ( params . reply_to ) . toBe ( "reply-guid" ) ;
1220+ } ) ;
1221+
1222+ it ( "strips reply_to when actions.reply is false" , async ( ) => {
1223+ const client = createClient ( { guid : "p:0/GUID" , status : "sent" } ) ;
1224+
1225+ await sendMessageIMessage ( "+15551234567" , "hello" , {
1226+ config : NO_REPLY_CFG ,
1227+ client,
1228+ replyToId : "reply-guid" ,
1229+ } ) ;
1230+
1231+ const params = ( client . request as ReturnType < typeof vi . fn > ) . mock . calls [ 0 ] [ 1 ] ;
1232+ expect ( params . reply_to ) . toBeUndefined ( ) ;
1233+ } ) ;
1234+
1235+ it ( "strips reply_to from media sends when actions.reply is false" , async ( ) => {
1236+ const client = createClient ( { guid : "p:0/GUID" , status : "sent" } ) ;
1237+ // send-attachment hits the imsg CLI which is unavailable in tests;
1238+ // provide a mock runCliJson to bypass the external binary.
1239+ const runCliJson = vi
1240+ . fn ( )
1241+ . mockRejectedValueOnce ( new Error ( "unknown command send-attachment" ) ) ;
1242+
1243+ await sendMessageIMessage ( "+15551234567" , "" , {
1244+ config : NO_REPLY_CFG ,
1245+ client,
1246+ replyToId : "reply-guid" ,
1247+ mediaUrl : "https://example.com/test.png" ,
1248+ resolveAttachmentImpl : async ( ) => ( { path : "/tmp/image.png" , contentType : "image/png" } ) ,
1249+ runCliJson,
1250+ } ) ;
1251+
1252+ // send-attachment failed → falls back to rpc send; reply_to must be absent.
1253+ const sendCalls = ( client . request as ReturnType < typeof vi . fn > ) . mock . calls . filter (
1254+ ( [ method ] : [ string ] ) => method === "send" ,
1255+ ) ;
1256+ for ( const [ , params ] of sendCalls ) {
1257+ expect ( params . reply_to ) . toBeUndefined ( ) ;
1258+ }
1259+ } ) ;
1260+
1261+ it ( "strips reply_to from attachment send-attachment when actions.reply is false and audioAsVoice" , async ( ) => {
1262+ // AudioAsVoice goes through send-attachment in spawn (needs mock runCliJson).
1263+ const runCliJson = vi . fn ( ) . mockResolvedValueOnce ( { guid : "p:0/GUID" , status : "sent" } ) ;
1264+
1265+ const result = await sendMessageIMessage ( "+15551234567" , "" , {
1266+ config : NO_REPLY_CFG ,
1267+ replyToId : "reply-guid" ,
1268+ mediaUrl : "https://example.com/test.caf" ,
1269+ audioAsVoice : true ,
1270+ resolveAttachmentImpl : async ( ) => ( { path : "/tmp/voice.caf" , contentType : "audio/x-caf" } ) ,
1271+ runCliJson,
1272+ } ) ;
1273+
1274+ expect ( result . messageId ) . toBe ( "p:0/GUID" ) ;
1275+ // runCliJson should have been called without --reply-to
1276+ const attachArgs = runCliJson . mock . calls [ 0 ] [ 0 ] ;
1277+ expect ( attachArgs ) . not . toContain ( "--reply-to" ) ;
1278+ } ) ;
1279+ } ) ;
11681280} ) ;
0 commit comments