Basic implementation
This commit is contained in:
parent
14fa650783
commit
5b85423b45
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -0,0 +1 @@
|
||||||
|
memory-mcp
|
71
README.md
Normal file
71
README.md
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
# Memory MCP - Fact Management System
|
||||||
|
|
||||||
|
A command-line application that runs an MCP (Model Context Protocol) server for managing and retrieving facts with associated keywords.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Store facts with associated keywords in a simple line-based format
|
||||||
|
- List all stored facts with their keywords
|
||||||
|
- Get a list of all unique keywords
|
||||||
|
- Find facts by specific keywords
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Ensure you have Go installed (version 1.21 or later recommended)
|
||||||
|
2. Clone this repository
|
||||||
|
3. Build the application:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build -o memory-mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. Start the MCP server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./memory-mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Connect to the server using any MCP client or integrate with LLMs that support the MCP protocol.
|
||||||
|
|
||||||
|
## Tools Available
|
||||||
|
|
||||||
|
The server provides the following tools:
|
||||||
|
|
||||||
|
### 1. Add Memory
|
||||||
|
|
||||||
|
Add a new fact with associated keywords.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- `fact`: The fact to remember (cannot contain semicolons)
|
||||||
|
- `keywords`: Comma-separated list of keywords
|
||||||
|
|
||||||
|
### 2. List All Memories
|
||||||
|
|
||||||
|
List all stored facts with their keywords.
|
||||||
|
|
||||||
|
### 3. List Unique Keywords
|
||||||
|
|
||||||
|
List all unique keywords used across all memories.
|
||||||
|
|
||||||
|
### 4. Find By Keyword
|
||||||
|
|
||||||
|
Find all facts associated with a specific keyword.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- `keyword`: Keyword to search for
|
||||||
|
|
||||||
|
## Memory Storage
|
||||||
|
|
||||||
|
Facts are stored in a simple text file located at `$XDG_STATE_HOME/memories.txt` or `~/.local/state/memories.txt` if the environment variable is not set.
|
||||||
|
|
||||||
|
Each memory is stored in the format:
|
||||||
|
```
|
||||||
|
fact;keyword1,keyword2,keyword3,...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
- Facts cannot contain semicolons (`;`) as this character is used as a separator
|
||||||
|
- The MCP server currently only supports stdio communication
|
13
go.mod
Normal file
13
go.mod
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
module memory-mcp
|
||||||
|
|
||||||
|
go 1.23
|
||||||
|
|
||||||
|
toolchain go1.24.3
|
||||||
|
|
||||||
|
require github.com/mark3labs/mcp-go v0.27.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/spf13/cast v1.7.1 // indirect
|
||||||
|
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||||
|
)
|
26
go.sum
Normal file
26
go.sum
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/mark3labs/mcp-go v0.27.0 h1:iok9kU4DUIU2/XVLgFS2Q9biIDqstC0jY4EQTK2Erzc=
|
||||||
|
github.com/mark3labs/mcp-go v0.27.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||||
|
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||||
|
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
289
main.go
Normal file
289
main.go
Normal file
|
@ -0,0 +1,289 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mark3labs/mcp-go/mcp"
|
||||||
|
"github.com/mark3labs/mcp-go/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MemoryManager handles storage and retrieval of facts with keywords
|
||||||
|
type MemoryManager struct {
|
||||||
|
filePath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Memory represents a fact with associated keywords
|
||||||
|
type Memory struct {
|
||||||
|
Fact string
|
||||||
|
Keywords []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMemoryManager creates a new memory manager
|
||||||
|
func NewMemoryManager() (*MemoryManager, error) {
|
||||||
|
// Get XDG_STATE_HOME or use default
|
||||||
|
stateHome := os.Getenv("XDG_STATE_HOME")
|
||||||
|
if stateHome == "" {
|
||||||
|
homeDir, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get home directory: %w", err)
|
||||||
|
}
|
||||||
|
stateHome = filepath.Join(homeDir, ".local", "state")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create directory if it doesn't exist
|
||||||
|
err := os.MkdirAll(stateHome, 0755)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create state directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &MemoryManager{
|
||||||
|
filePath: filepath.Join(stateHome, "memories.txt"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddMemory adds a new memory
|
||||||
|
func (m *MemoryManager) AddMemory(fact string, keywords []string) error {
|
||||||
|
if strings.Contains(fact, ";") {
|
||||||
|
return errors.New("fact cannot contain semicolon (;)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format: fact;keyword1,keyword2,...
|
||||||
|
line := fact + ";" + strings.Join(keywords, ",") + "\n"
|
||||||
|
|
||||||
|
// Open file in append mode
|
||||||
|
file, err := os.OpenFile(m.filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open memory file: %w", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
_, err = file.WriteString(line)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to write memory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllMemories returns all stored memories
|
||||||
|
func (m *MemoryManager) GetAllMemories() ([]Memory, error) {
|
||||||
|
return m.loadMemories()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUniqueKeywords returns a list of all unique keywords
|
||||||
|
func (m *MemoryManager) GetUniqueKeywords() ([]string, error) {
|
||||||
|
memories, err := m.loadMemories()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a map to track unique keywords
|
||||||
|
keywordMap := make(map[string]bool)
|
||||||
|
for _, memory := range memories {
|
||||||
|
for _, keyword := range memory.Keywords {
|
||||||
|
keywordMap[keyword] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert map keys to slice
|
||||||
|
keywords := make([]string, 0, len(keywordMap))
|
||||||
|
for keyword := range keywordMap {
|
||||||
|
keywords = append(keywords, keyword)
|
||||||
|
}
|
||||||
|
|
||||||
|
return keywords, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMemoriesByKeyword returns all memories that have the given keyword
|
||||||
|
func (m *MemoryManager) GetMemoriesByKeyword(keyword string) ([]Memory, error) {
|
||||||
|
memories, err := m.loadMemories()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter memories by keyword
|
||||||
|
var filtered []Memory
|
||||||
|
for _, memory := range memories {
|
||||||
|
for _, k := range memory.Keywords {
|
||||||
|
if k == keyword {
|
||||||
|
filtered = append(filtered, memory)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadMemories loads all memories from the file
|
||||||
|
func (m *MemoryManager) loadMemories() ([]Memory, error) {
|
||||||
|
// Check if file exists
|
||||||
|
if _, err := os.Stat(m.filePath); os.IsNotExist(err) {
|
||||||
|
// Return empty slice if file doesn't exist
|
||||||
|
return []Memory{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read file content
|
||||||
|
content, err := os.ReadFile(m.filePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read memory file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var memories []Memory
|
||||||
|
lines := strings.Split(string(content), "\n")
|
||||||
|
|
||||||
|
for _, line := range lines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(line, ";")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
continue // Skip invalid lines
|
||||||
|
}
|
||||||
|
|
||||||
|
fact := parts[0]
|
||||||
|
keywords := strings.Split(parts[1], ",")
|
||||||
|
|
||||||
|
// Filter out empty keywords
|
||||||
|
var cleanKeywords []string
|
||||||
|
for _, k := range keywords {
|
||||||
|
k = strings.TrimSpace(k)
|
||||||
|
if k != "" {
|
||||||
|
cleanKeywords = append(cleanKeywords, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
memories = append(memories, Memory{
|
||||||
|
Fact: fact,
|
||||||
|
Keywords: cleanKeywords,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return memories, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("Memory MCP - Management Control Program")
|
||||||
|
|
||||||
|
// Create memory manager
|
||||||
|
memoryManager, err := NewMemoryManager()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error initializing memory manager: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create MCP server
|
||||||
|
s := server.NewMCPServer(
|
||||||
|
"Memory Manager",
|
||||||
|
"1.0.0",
|
||||||
|
server.WithToolCapabilities(true),
|
||||||
|
server.WithRecovery(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tool to add a new memory
|
||||||
|
addMemoryTool := mcp.NewTool("add_memory",
|
||||||
|
mcp.WithDescription("Add a new fact with associated keywords"),
|
||||||
|
mcp.WithString("fact", mcp.Required(), mcp.Description("The fact to remember (cannot contain semicolons)")),
|
||||||
|
mcp.WithString("keywords", mcp.Required(), mcp.Description("Comma-separated list of keywords")),
|
||||||
|
)
|
||||||
|
|
||||||
|
s.AddTool(addMemoryTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
|
fact := request.Params.Arguments["fact"].(string)
|
||||||
|
keywordsStr := request.Params.Arguments["keywords"].(string)
|
||||||
|
|
||||||
|
keywords := strings.Split(keywordsStr, ",")
|
||||||
|
for i, k := range keywords {
|
||||||
|
keywords[i] = strings.TrimSpace(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := memoryManager.AddMemory(fact, keywords)
|
||||||
|
if err != nil {
|
||||||
|
return mcp.NewToolResultError(fmt.Sprintf("Failed to add memory: %v", err)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return mcp.NewToolResultText(fmt.Sprintf("Memory added: %s", fact)), nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// Tool to list all memories
|
||||||
|
listMemoriesTool := mcp.NewTool("list_all_memories",
|
||||||
|
mcp.WithDescription("List all stored facts with their keywords"),
|
||||||
|
)
|
||||||
|
|
||||||
|
s.AddTool(listMemoriesTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
|
memories, err := memoryManager.GetAllMemories()
|
||||||
|
if err != nil {
|
||||||
|
return mcp.NewToolResultError(fmt.Sprintf("Failed to retrieve memories: %v", err)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(memories) == 0 {
|
||||||
|
return mcp.NewToolResultText("No memories stored yet."), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result strings.Builder
|
||||||
|
for i, memory := range memories {
|
||||||
|
result.WriteString(fmt.Sprintf("%d. Fact: %s\n Keywords: %s\n",
|
||||||
|
i+1, memory.Fact, strings.Join(memory.Keywords, ", ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
return mcp.NewToolResultText(result.String()), nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// Tool to list unique keywords
|
||||||
|
listKeywordsTool := mcp.NewTool("list_unique_keywords",
|
||||||
|
mcp.WithDescription("List all unique keywords used across all memories"),
|
||||||
|
)
|
||||||
|
|
||||||
|
s.AddTool(listKeywordsTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
|
keywords, err := memoryManager.GetUniqueKeywords()
|
||||||
|
if err != nil {
|
||||||
|
return mcp.NewToolResultError(fmt.Sprintf("Failed to retrieve keywords: %v", err)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(keywords) == 0 {
|
||||||
|
return mcp.NewToolResultText("No keywords found."), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return mcp.NewToolResultText(fmt.Sprintf("Unique keywords: %s", strings.Join(keywords, ", "))), nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// Tool to find memories by keyword
|
||||||
|
findByKeywordTool := mcp.NewTool("find_by_keyword",
|
||||||
|
mcp.WithDescription("Find all facts associated with a specific keyword"),
|
||||||
|
mcp.WithString("keyword", mcp.Required(), mcp.Description("Keyword to search for")),
|
||||||
|
)
|
||||||
|
|
||||||
|
s.AddTool(findByKeywordTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
|
keyword := request.Params.Arguments["keyword"].(string)
|
||||||
|
memories, err := memoryManager.GetMemoriesByKeyword(keyword)
|
||||||
|
if err != nil {
|
||||||
|
return mcp.NewToolResultError(fmt.Sprintf("Failed to search memories: %v", err)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(memories) == 0 {
|
||||||
|
return mcp.NewToolResultText(fmt.Sprintf("No memories found with keyword '%s'.", keyword)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result strings.Builder
|
||||||
|
result.WriteString(fmt.Sprintf("Found %d memories with keyword '%s':\n\n", len(memories), keyword))
|
||||||
|
|
||||||
|
for i, memory := range memories {
|
||||||
|
result.WriteString(fmt.Sprintf("%d. Fact: %s\n Keywords: %s\n",
|
||||||
|
i+1, memory.Fact, strings.Join(memory.Keywords, ", ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
return mcp.NewToolResultText(result.String()), nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// Start the server
|
||||||
|
fmt.Println("Starting Memory Manager MCP server...")
|
||||||
|
if err := server.ServeStdio(s); err != nil {
|
||||||
|
fmt.Printf("Server error: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
102
memory_manager_test.go
Normal file
102
memory_manager_test.go
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMemoryManager(t *testing.T) {
|
||||||
|
// Create a temporary directory for testing
|
||||||
|
tmpDir, err := os.MkdirTemp("", "memory-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create temp directory: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
// Set XDG_STATE_HOME to our temp directory
|
||||||
|
oldStateHome := os.Getenv("XDG_STATE_HOME")
|
||||||
|
os.Setenv("XDG_STATE_HOME", tmpDir)
|
||||||
|
defer os.Setenv("XDG_STATE_HOME", oldStateHome)
|
||||||
|
|
||||||
|
// Create memory manager
|
||||||
|
memoryManager, err := NewMemoryManager()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create memory manager: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test adding memories
|
||||||
|
testData := []struct {
|
||||||
|
fact string
|
||||||
|
keywords []string
|
||||||
|
}{
|
||||||
|
{"The sky is blue", []string{"sky", "blue", "nature"}},
|
||||||
|
{"Cats are mammals", []string{"cats", "mammals", "animals"}},
|
||||||
|
{"Water freezes at 0°C", []string{"water", "ice", "temperature", "science"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, data := range testData {
|
||||||
|
err := memoryManager.AddMemory(data.fact, data.keywords)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to add memory '%s': %v", data.fact, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test retrieving all memories
|
||||||
|
memories, err := memoryManager.GetAllMemories()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to retrieve memories: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(memories) != len(testData) {
|
||||||
|
t.Errorf("Expected %d memories, got %d", len(testData), len(memories))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test retrieving unique keywords
|
||||||
|
keywords, err := memoryManager.GetUniqueKeywords()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to retrieve keywords: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expected number of unique keywords
|
||||||
|
expectedKeywords := map[string]bool{
|
||||||
|
"sky": true, "blue": true, "nature": true,
|
||||||
|
"cats": true, "mammals": true, "animals": true,
|
||||||
|
"water": true, "ice": true, "temperature": true, "science": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(keywords) != len(expectedKeywords) {
|
||||||
|
t.Errorf("Expected %d unique keywords, got %d", len(expectedKeywords), len(keywords))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test retrieving memories by keyword
|
||||||
|
testKeyword := "science"
|
||||||
|
byKeyword, err := memoryManager.GetMemoriesByKeyword(testKeyword)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to retrieve memories by keyword: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(byKeyword) != 1 {
|
||||||
|
t.Errorf("Expected 1 memory with keyword '%s', got %d", testKeyword, len(byKeyword))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(byKeyword) > 0 && byKeyword[0].Fact != "Water freezes at 0°C" {
|
||||||
|
t.Errorf("Expected to find 'Water freezes at 0°C' with keyword '%s', got '%s'",
|
||||||
|
testKeyword, byKeyword[0].Fact)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the file is created and contains the correct data
|
||||||
|
filePath := filepath.Join(tmpDir, "memories.txt")
|
||||||
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||||
|
t.Errorf("Memory file was not created")
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := os.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to read memory file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(content) == 0 {
|
||||||
|
t.Errorf("Memory file is empty")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue