55// -----------------------------------------------------------------------
66using Microsoft . Extensions . DependencyInjection ;
77using Netclaw . Actors . Channels ;
8+ using Netclaw . Cli . Config ;
89using Netclaw . Cli . Discord ;
910using Netclaw . Cli . Tests . Tui ;
1011using Netclaw . Cli . Tui ;
@@ -96,8 +97,8 @@ public async Task Channels_RotateCredentials_AcceptsTypedCredentialInput(Channel
9697 await app . RunAsync ( cts . Token ) ;
9798
9899 var channelsVm = Assert . IsType < ChannelsConfigViewModel > ( getChannelsVm ( ) ) ;
99- AssertTypedCredentials ( channelsVm , channelType ) ;
100- Assert . Equal ( "Credential changes staged. Press Esc, then s to save ." , channelsVm . Status . Value . Text ) ;
100+ AssertPersistedCredentials ( channelType , typed : true ) ;
101+ Assert . Equal ( "Credential changes saved ." , channelsVm . Status . Value . Text ) ;
101102 }
102103
103104 [ Theory ]
@@ -121,7 +122,7 @@ public async Task Channels_FirstTimeAdapterSetup_AcceptsTypedCredentialInput(Cha
121122 var channelsVm = Assert . IsType < ChannelsConfigViewModel > ( getChannelsVm ( ) ) ;
122123 Assert . Equal ( ChannelsConfigScreen . ChannelPermissions , channelsVm . Screen . Value ) ;
123124 Assert . Equal ( channelType , channelsVm . ActiveAdapterType ) ;
124- AssertFirstTimeSetup ( channelsVm , channelType ) ;
125+ AssertFirstTimeSetupPersisted ( channelsVm , channelType ) ;
125126 }
126127
127128 [ Fact ]
@@ -156,16 +157,16 @@ public async Task Channels_AddChannel_AcceptsPastedChannelInput()
156157 input . EnqueueKey ( ConsoleKey . Enter ) ; // Open configured Slack management.
157158 input . EnqueueKey ( ConsoleKey . DownArrow ) ; // Add channel.
158159 input . EnqueueKey ( ConsoleKey . Enter ) ;
159- input . EnqueuePaste ( "#pasted-channel " ) ;
160+ input . EnqueuePaste ( "#C09 " ) ;
160161 input . EnqueueKey ( ConsoleKey . Enter ) ;
161162 input . EnqueueKey ( ConsoleKey . Q , false , false , true ) ;
162163
163164 using var cts = new CancellationTokenSource ( TimeSpan . FromSeconds ( 10 ) ) ;
164165 await app . RunAsync ( cts . Token ) ;
165166
166167 var channelsVm = Assert . IsType < ChannelsConfigViewModel > ( getChannelsVm ( ) ) ;
167- Assert . Contains ( channelsVm . GetChannelRows ( ) , row => row . Id == "pasted-channel " && ! row . IsAddAction ) ;
168- Assert . Equal ( "Added pasted-channel. Press Esc, then s to save ." , channelsVm . Status . Value . Text ) ;
168+ Assert . Contains ( channelsVm . GetChannelRows ( ) , row => row . Id == "C09 " && ! row . IsAddAction ) ;
169+ Assert . Equal ( "Added C09 and saved ." , channelsVm . Status . Value . Text ) ;
169170 }
170171
171172 [ Fact ]
@@ -202,7 +203,7 @@ public async Task Channels_ChannelPermissions_DeleteRemovesSelectedChannel()
202203
203204 var channelsVm = Assert . IsType < ChannelsConfigViewModel > ( getChannelsVm ( ) ) ;
204205 Assert . DoesNotContain ( channelsVm . GetChannelRows ( ) , row => row . Id == "C01" ) ;
205- Assert . Equal ( "Removed C01. Press Esc, then s to save ." , channelsVm . Status . Value . Text ) ;
206+ Assert . Equal ( "Removed C01 and saved ." , channelsVm . Status . Value . Text ) ;
206207 }
207208
208209 [ Fact ]
@@ -346,7 +347,7 @@ private static void TypeFirstTimeSetup(VirtualInputSource input, ChannelType cha
346347 input . EnqueueKey ( ConsoleKey . Enter ) ;
347348 input . EnqueueString ( "xapp-first-time-token" ) ;
348349 input . EnqueueKey ( ConsoleKey . Enter ) ;
349- input . EnqueueString ( "C-first-time " ) ;
350+ input . EnqueueString ( "C123456 " ) ;
350351 input . EnqueueKey ( ConsoleKey . Enter ) ;
351352 SelectSecondOption ( input ) ; // Disable DMs.
352353 SelectSecondOption ( input ) ; // Allow anyone in allowed channels.
@@ -381,55 +382,73 @@ private static void SelectSecondOption(VirtualInputSource input)
381382 input . EnqueueKey ( ConsoleKey . Enter ) ;
382383 }
383384
384- private static void AssertTypedCredentials ( ChannelsConfigViewModel vm , ChannelType channelType )
385+ private void AssertPersistedCredentials ( ChannelType channelType , bool typed )
385386 {
387+ var config = ConfigFileHelper . LoadJsonDict ( _paths . NetclawConfigPath ) ;
388+ var secrets = ConfigFileHelper . LoadJsonDict ( _paths . SecretsPath ) ;
386389 switch ( channelType )
387390 {
388391 case ChannelType . Slack :
389- var slack = vm . Step . GetAdapterViewModel < SlackStepViewModel > ( ChannelType . Slack ) ;
390- Assert . Equal ( "xoxb-typed-token" , slack . BotToken ) ;
391- Assert . Equal ( "xapp-typed-token" , slack . AppToken ) ;
392+ AssertSecret ( secrets , "Slack.BotToken" , typed ? "xoxb-typed-token" : "xoxb-first-time-token" ) ;
393+ AssertSecret ( secrets , "Slack.AppToken" , typed ? "xapp-typed-token" : "xapp-first-time-token" ) ;
392394 break ;
393395 case ChannelType . Discord :
394- var discord = vm . Step . GetAdapterViewModel < DiscordStepViewModel > ( ChannelType . Discord ) ;
395- Assert . Equal ( "discord-typed-token" , discord . BotToken ) ;
396+ AssertSecret ( secrets , "Discord.BotToken" , typed ? "discord-typed-token" : "discord-first-time-token" ) ;
396397 break ;
397398 case ChannelType . Mattermost :
398- var mattermost = vm . Step . GetAdapterViewModel < MattermostStepViewModel > ( ChannelType . Mattermost ) ;
399- Assert . Equal ( "https://typed-mattermost.example.com" , mattermost . ServerUrl ) ;
400- Assert . Equal ( " mattermost-typed-token", mattermost . BotToken ) ;
399+ Assert . True ( ConfigFileHelper . TryGetPathValue ( config , " Mattermost.ServerUrl" , out var serverUrl ) ) ;
400+ Assert . Equal ( typed ? "https://typed-mattermost.example.com" : "https://first-time- mattermost.example.com" , serverUrl ) ;
401+ AssertSecret ( secrets , "Mattermost.BotToken" , typed ? " mattermost-typed-token" : " mattermost-first-time-token" ) ;
401402 break ;
402403 default :
403404 throw new ArgumentOutOfRangeException ( nameof ( channelType ) , channelType , null ) ;
404405 }
405406 }
406407
407- private static void AssertFirstTimeSetup ( ChannelsConfigViewModel vm , ChannelType channelType )
408+ private void AssertFirstTimeSetupPersisted ( ChannelsConfigViewModel vm , ChannelType channelType )
408409 {
410+ AssertPersistedCredentials ( channelType , typed : false ) ;
411+ var config = ConfigFileHelper . LoadJsonDict ( _paths . NetclawConfigPath ) ;
409412 switch ( channelType )
410413 {
411414 case ChannelType . Slack :
412415 var slack = vm . Step . GetAdapterViewModel < SlackStepViewModel > ( ChannelType . Slack ) ;
413- Assert . Equal ( "xoxb-first-time-token" , slack . BotToken ) ;
414- Assert . Equal ( "xapp-first-time-token" , slack . AppToken ) ;
415- Assert . Equal ( "C-first-time" , slack . ChannelNamesInput ) ;
416+ Assert . True ( slack . HasPersistedBotToken ) ;
417+ Assert . True ( slack . HasPersistedAppToken ) ;
418+ Assert . True ( ConfigFileHelper . TryGetPathValue ( config , "Slack.AllowedChannelIds" , out var slackChannelsRaw ) ) ;
419+ Assert . Equal ( [ "C123456" ] , ToStringArray ( slackChannelsRaw ) ) ;
416420 break ;
417421 case ChannelType . Discord :
418422 var discord = vm . Step . GetAdapterViewModel < DiscordStepViewModel > ( ChannelType . Discord ) ;
419- Assert . Equal ( "discord-first-time-token" , discord . BotToken ) ;
420- Assert . Equal ( "123456789012345678" , discord . ChannelIdsInput ) ;
423+ Assert . True ( discord . HasPersistedBotToken ) ;
424+ Assert . True ( ConfigFileHelper . TryGetPathValue ( config , "Discord.AllowedChannelIds" , out var discordChannelsRaw ) ) ;
425+ Assert . Equal ( [ "123456789012345678" ] , ToStringArray ( discordChannelsRaw ) ) ;
421426 break ;
422427 case ChannelType . Mattermost :
423428 var mattermost = vm . Step . GetAdapterViewModel < MattermostStepViewModel > ( ChannelType . Mattermost ) ;
424- Assert . Equal ( "https://first-time- mattermost.example.com" , mattermost . ServerUrl ) ;
425- Assert . Equal ( "mattermost-first-time-token ", mattermost . BotToken ) ;
426- Assert . Equal ( "town-square" , mattermost . ChannelIdsInput ) ;
429+ Assert . True ( mattermost . HasPersistedBotToken ) ;
430+ Assert . True ( ConfigFileHelper . TryGetPathValue ( config , "Mattermost.AllowedChannelIds ", out var mattermostChannelsRaw ) ) ;
431+ Assert . Equal ( [ "town-square" ] , ToStringArray ( mattermostChannelsRaw ) ) ;
427432 break ;
428433 default :
429434 throw new ArgumentOutOfRangeException ( nameof ( channelType ) , channelType , null ) ;
430435 }
431436 }
432437
438+ private void AssertSecret ( Dictionary < string , object > secrets , string path , string expected )
439+ {
440+ Assert . True ( ConfigFileHelper . TryGetPathValue ( secrets , path , out var raw ) ) ;
441+ Assert . Equal ( expected , ConfigFileHelper . DecryptIfEncrypted ( _paths , raw ? . ToString ( ) ) ) ;
442+ }
443+
444+ private static string [ ] ToStringArray ( object ? raw )
445+ => Assert . IsType < object [ ] > ( raw ) . Select ( static value => value switch
446+ {
447+ string text => text ,
448+ System . Text . Json . JsonElement { ValueKind : System . Text . Json . JsonValueKind . String } element => element . GetString ( ) ! ,
449+ _ => throw new InvalidOperationException ( "Expected string array value." )
450+ } ) . ToArray ( ) ;
451+
433452 private void WriteEmptyChannelFiles ( )
434453 {
435454 File . WriteAllText ( _paths . NetclawConfigPath ,
0 commit comments