1818import static org .junit .jupiter .api .Assertions .assertEquals ;
1919import static org .junit .jupiter .api .Assertions .assertFalse ;
2020import static org .junit .jupiter .api .Assertions .assertTrue ;
21-
21+ import static org .mockito .ArgumentMatchers .any ;
22+ import static org .mockito .ArgumentMatchers .anyList ;
23+ import static org .mockito .Mockito .mock ;
24+ import static org .mockito .Mockito .when ;
25+
26+ import io .agentscope .core .message .TextBlock ;
27+ import io .agentscope .core .model .ChatResponse ;
28+ import io .agentscope .core .model .Model ;
29+ import io .agentscope .harness .agent .filesystem .local .LocalFilesystem ;
2230import io .agentscope .harness .agent .filesystem .remote .RemoteFilesystem ;
2331import io .agentscope .harness .agent .store .InMemoryStore ;
2432import io .agentscope .harness .agent .workspace .WorkspaceManager ;
2937import java .util .Map ;
3038import org .junit .jupiter .api .Test ;
3139import org .junit .jupiter .api .io .TempDir ;
40+ import reactor .core .publisher .Flux ;
3241
3342/**
3443 * Verifies that {@link MemoryConsolidator} reads daily ledgers and writes watermark / MEMORY.md
35- * entirely through {@link io.agentscope.harness.agent.filesystem.AbstractFilesystem}, making it
36- * backend-agnostic.
44+ * through the filesystem layer.
3745 */
3846class MemoryConsolidatorFilesystemTest {
3947
@@ -50,10 +58,6 @@ private static void seedStoreFile(
5058 store .put (ns , path , value );
5159 }
5260
53- // ======================================================================
54- // readWatermark: returns EPOCH when state file absent
55- // ======================================================================
56-
5761 @ Test
5862 void readWatermark_returnsEpochWhenStateAbsent (@ TempDir Path tmp ) {
5963 InMemoryStore store = new InMemoryStore ();
@@ -66,10 +70,6 @@ void readWatermark_returnsEpochWhenStateAbsent(@TempDir Path tmp) {
6670 assertEquals (Instant .EPOCH , consolidator .readWatermark ());
6771 }
6872
69- // ======================================================================
70- // readWatermark / writeWatermark round-trip through filesystem
71- // ======================================================================
72-
7373 @ Test
7474 void watermark_roundTripThroughFilesystem (@ TempDir Path tmp ) {
7575 InMemoryStore store = new InMemoryStore ();
@@ -85,10 +85,6 @@ void watermark_roundTripThroughFilesystem(@TempDir Path tmp) {
8585 assertEquals (ts , consolidator .readWatermark ());
8686 }
8787
88- // ======================================================================
89- // readWatermark: no local file is touched — only the filesystem
90- // ======================================================================
91-
9288 @ Test
9389 void watermark_doesNotCreateLocalFile (@ TempDir Path tmp ) {
9490 InMemoryStore store = new InMemoryStore ();
@@ -101,48 +97,65 @@ void watermark_doesNotCreateLocalFile(@TempDir Path tmp) {
10197 Instant ts = Instant .now ();
10298 wsm .writeUtf8WorkspaceRelative (MemoryConsolidator .STATE_REL_PATH , ts .toString ());
10399
104- // local disk must NOT have the state file — it lives only in the store
105100 Path localState = tmp .resolve ("memory" ).resolve (MemoryConsolidator .STATE_FILE );
106101 assertFalse (
107102 Files .exists (localState ),
108103 "state file should not be written to local disk when using RemoteFilesystem" );
109104
110- // but consolidator reads it correctly from the store
111105 assertEquals (ts , consolidator .readWatermark ());
112106 }
113107
114- // ======================================================================
115- // STATE_FILE constant is preserved
116- // ======================================================================
117-
118108 @ Test
119109 void stateFileRelPath_matchesConstant () {
120110 assertEquals ("memory/" + MemoryConsolidator .STATE_FILE , MemoryConsolidator .STATE_REL_PATH );
121111 }
122112
123- // ======================================================================
124- // Local filesystem (no store) — watermark uses local disk via WorkspaceManager
125- // ======================================================================
113+ @ Test
114+ void consolidate_readsRootDailyLedgerAndWritesMemoryMd (@ TempDir Path tmp ) throws Exception {
115+ LocalFilesystem fs = new LocalFilesystem (tmp );
116+ WorkspaceManager wsm = new WorkspaceManager (tmp , fs );
117+
118+ Path memoryDir = Files .createDirectories (tmp .resolve ("memory" ));
119+ Files .writeString (memoryDir .resolve ("2026-05-20.md" ), "root daily entry" );
120+
121+ MemoryConsolidator consolidator = new MemoryConsolidator (wsm , stubModel ("updated memory" ));
122+
123+ consolidator .consolidate ().block ();
124+
125+ assertEquals ("updated memory" , wsm .readMemoryMd ());
126+ assertTrue (consolidator .readWatermark ().isAfter (Instant .EPOCH ));
127+ }
126128
127129 @ Test
128130 void watermark_localFallback_whenNoFilesystem (@ TempDir Path tmp ) throws Exception {
129131 WorkspaceManager wsm = new WorkspaceManager (tmp );
130132
131133 MemoryConsolidator consolidator = new MemoryConsolidator (wsm , null );
132134
133- // No file → EPOCH
134135 assertEquals (Instant .EPOCH , consolidator .readWatermark ());
135136
136- // Write via WorkspaceManager (falls to local disk)
137137 Instant ts = Instant .parse ("2025-03-10T09:00:00Z" );
138138 wsm .writeUtf8WorkspaceRelative (MemoryConsolidator .STATE_REL_PATH , ts .toString ());
139139
140140 assertEquals (ts , consolidator .readWatermark ());
141141
142- // Verify the local file actually exists
143142 Path localState = tmp .resolve ("memory" ).resolve (MemoryConsolidator .STATE_FILE );
144143 assertTrue (
145144 Files .exists (localState ),
146145 "state file should be written to local disk when no filesystem is configured" );
147146 }
147+
148+ private static Model stubModel (String assistantText ) {
149+ Model model = mock (Model .class );
150+ when (model .getModelName ()).thenReturn ("stub-model" );
151+ ChatResponse chunk =
152+ new ChatResponse (
153+ "stub-id" ,
154+ List .of (TextBlock .builder ().text (assistantText ).build ()),
155+ null ,
156+ Map .of (),
157+ "stop" );
158+ when (model .stream (anyList (), any (), any ())).thenReturn (Flux .just (chunk ));
159+ return model ;
160+ }
148161}
0 commit comments