mocks_test.go

  1package tools
  2
  3import (
  4	"context"
  5	"fmt"
  6	"sort"
  7	"strconv"
  8	"strings"
  9	"time"
 10
 11	"github.com/google/uuid"
 12	"github.com/kujtimiihoxha/termai/internal/history"
 13	"github.com/kujtimiihoxha/termai/internal/permission"
 14	"github.com/kujtimiihoxha/termai/internal/pubsub"
 15)
 16
 17// Mock permission service for testing
 18type mockPermissionService struct {
 19	*pubsub.Broker[permission.PermissionRequest]
 20	allow bool
 21}
 22
 23func (m *mockPermissionService) GrantPersistant(permission permission.PermissionRequest) {
 24	// Not needed for tests
 25}
 26
 27func (m *mockPermissionService) Grant(permission permission.PermissionRequest) {
 28	// Not needed for tests
 29}
 30
 31func (m *mockPermissionService) Deny(permission permission.PermissionRequest) {
 32	// Not needed for tests
 33}
 34
 35func (m *mockPermissionService) Request(opts permission.CreatePermissionRequest) bool {
 36	return m.allow
 37}
 38
 39func newMockPermissionService(allow bool) permission.Service {
 40	return &mockPermissionService{
 41		Broker: pubsub.NewBroker[permission.PermissionRequest](),
 42		allow:  allow,
 43	}
 44}
 45
 46type mockFileHistoryService struct {
 47	*pubsub.Broker[history.File]
 48	files     map[string]history.File // ID -> File
 49	timeNow   func() int64
 50}
 51
 52// Create implements history.Service.
 53func (m *mockFileHistoryService) Create(ctx context.Context, sessionID string, path string, content string) (history.File, error) {
 54	return m.createWithVersion(ctx, sessionID, path, content, history.InitialVersion)
 55}
 56
 57// CreateVersion implements history.Service.
 58func (m *mockFileHistoryService) CreateVersion(ctx context.Context, sessionID string, path string, content string) (history.File, error) {
 59	var files []history.File
 60	for _, file := range m.files {
 61		if file.Path == path {
 62			files = append(files, file)
 63		}
 64	}
 65
 66	if len(files) == 0 {
 67		// No previous versions, create initial
 68		return m.Create(ctx, sessionID, path, content)
 69	}
 70
 71	// Sort files by CreatedAt in descending order
 72	sort.Slice(files, func(i, j int) bool {
 73		return files[i].CreatedAt > files[j].CreatedAt
 74	})
 75
 76	// Get the latest version
 77	latestFile := files[0]
 78	latestVersion := latestFile.Version
 79
 80	// Generate the next version
 81	var nextVersion string
 82	if latestVersion == history.InitialVersion {
 83		nextVersion = "v1"
 84	} else if strings.HasPrefix(latestVersion, "v") {
 85		versionNum, err := strconv.Atoi(latestVersion[1:])
 86		if err != nil {
 87			// If we can't parse the version, just use a timestamp-based version
 88			nextVersion = fmt.Sprintf("v%d", latestFile.CreatedAt)
 89		} else {
 90			nextVersion = fmt.Sprintf("v%d", versionNum+1)
 91		}
 92	} else {
 93		// If the version format is unexpected, use a timestamp-based version
 94		nextVersion = fmt.Sprintf("v%d", latestFile.CreatedAt)
 95	}
 96
 97	return m.createWithVersion(ctx, sessionID, path, content, nextVersion)
 98}
 99
100func (m *mockFileHistoryService) createWithVersion(_ context.Context, sessionID, path, content, version string) (history.File, error) {
101	now := m.timeNow()
102	file := history.File{
103		ID:        uuid.New().String(),
104		SessionID: sessionID,
105		Path:      path,
106		Content:   content,
107		Version:   version,
108		CreatedAt: now,
109		UpdatedAt: now,
110	}
111
112	m.files[file.ID] = file
113	m.Publish(pubsub.CreatedEvent, file)
114	return file, nil
115}
116
117// Delete implements history.Service.
118func (m *mockFileHistoryService) Delete(ctx context.Context, id string) error {
119	file, ok := m.files[id]
120	if !ok {
121		return fmt.Errorf("file not found: %s", id)
122	}
123
124	delete(m.files, id)
125	m.Publish(pubsub.DeletedEvent, file)
126	return nil
127}
128
129// DeleteSessionFiles implements history.Service.
130func (m *mockFileHistoryService) DeleteSessionFiles(ctx context.Context, sessionID string) error {
131	files, err := m.ListBySession(ctx, sessionID)
132	if err != nil {
133		return err
134	}
135
136	for _, file := range files {
137		err = m.Delete(ctx, file.ID)
138		if err != nil {
139			return err
140		}
141	}
142
143	return nil
144}
145
146// Get implements history.Service.
147func (m *mockFileHistoryService) Get(ctx context.Context, id string) (history.File, error) {
148	file, ok := m.files[id]
149	if !ok {
150		return history.File{}, fmt.Errorf("file not found: %s", id)
151	}
152	return file, nil
153}
154
155// GetByPathAndSession implements history.Service.
156func (m *mockFileHistoryService) GetByPathAndSession(ctx context.Context, path string, sessionID string) (history.File, error) {
157	var latestFile history.File
158	var found bool
159	var latestTime int64
160
161	for _, file := range m.files {
162		if file.Path == path && file.SessionID == sessionID {
163			if !found || file.CreatedAt > latestTime {
164				latestFile = file
165				latestTime = file.CreatedAt
166				found = true
167			}
168		}
169	}
170
171	if !found {
172		return history.File{}, fmt.Errorf("file not found: %s for session %s", path, sessionID)
173	}
174	return latestFile, nil
175}
176
177// ListBySession implements history.Service.
178func (m *mockFileHistoryService) ListBySession(ctx context.Context, sessionID string) ([]history.File, error) {
179	var files []history.File
180	for _, file := range m.files {
181		if file.SessionID == sessionID {
182			files = append(files, file)
183		}
184	}
185
186	// Sort by CreatedAt in descending order
187	sort.Slice(files, func(i, j int) bool {
188		return files[i].CreatedAt > files[j].CreatedAt
189	})
190
191	return files, nil
192}
193
194// ListLatestSessionFiles implements history.Service.
195func (m *mockFileHistoryService) ListLatestSessionFiles(ctx context.Context, sessionID string) ([]history.File, error) {
196	// Map to track the latest file for each path
197	latestFiles := make(map[string]history.File)
198	
199	for _, file := range m.files {
200		if file.SessionID == sessionID {
201			existing, ok := latestFiles[file.Path]
202			if !ok || file.CreatedAt > existing.CreatedAt {
203				latestFiles[file.Path] = file
204			}
205		}
206	}
207
208	// Convert map to slice
209	var result []history.File
210	for _, file := range latestFiles {
211		result = append(result, file)
212	}
213
214	// Sort by CreatedAt in descending order
215	sort.Slice(result, func(i, j int) bool {
216		return result[i].CreatedAt > result[j].CreatedAt
217	})
218
219	return result, nil
220}
221
222// Subscribe implements history.Service.
223func (m *mockFileHistoryService) Subscribe(ctx context.Context) <-chan pubsub.Event[history.File] {
224	return m.Broker.Subscribe(ctx)
225}
226
227// Update implements history.Service.
228func (m *mockFileHistoryService) Update(ctx context.Context, file history.File) (history.File, error) {
229	_, ok := m.files[file.ID]
230	if !ok {
231		return history.File{}, fmt.Errorf("file not found: %s", file.ID)
232	}
233
234	file.UpdatedAt = m.timeNow()
235	m.files[file.ID] = file
236	m.Publish(pubsub.UpdatedEvent, file)
237	return file, nil
238}
239
240func newMockFileHistoryService() history.Service {
241	return &mockFileHistoryService{
242		Broker:   pubsub.NewBroker[history.File](),
243		files:    make(map[string]history.File),
244		timeNow:  func() int64 { return time.Now().Unix() },
245	}
246}