GitRoot

Craft your forge, Build your project, Grow your community freely
  1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
  2//
  3// SPDX-License-Identifier: MIT
  4
  5package test
  6
  7import (
  8	"bytes"
  9	"errors"
 10	"io/fs"
 11	"os"
 12	"path/filepath"
 13	"testing"
 14
 15	"gitroot.dev/libs/golang/plugin/model"
 16)
 17
 18type fakeServer struct {
 19	t         *testing.T
 20	dirGit    string
 21	fsGit     model.FsBase
 22	grfsGit   *model.GrFs
 23	dirWeb    string
 24	fsWeb     model.FsBase
 25	grfsWeb   *model.GrFs
 26	dirCache  string
 27	fsCache   model.FsBase
 28	grfsCache *model.GrFs
 29	override  Override
 30	NbCall    nbCall
 31}
 32
 33type Override struct {
 34	PluginOption       func(opts ...model.PluginOptionWith)
 35	ForgeConf          func() (model.ForgeConf, error)
 36	Worktree           func() *model.GrFs
 37	Webcontent         func() *model.GrFs
 38	Cache              func() *model.GrFs
 39	ModifyContent      func(path string, content string)
 40	ModifyWebContent   func(filepath, content string)
 41	ReplaceWebContent  func(filepath, oldContent, content string)
 42	ModifyCacheContent func(filepath, content string)
 43	CopyFile           func(fromFs model.FsBase, fromPath string, toFs model.FsBase, toPath string) error
 44	DeleteFile         func(fromFs model.FsBase, filename string) error
 45	MoveFile           func(fromFs model.FsBase, fromPath string, toFs model.FsBase, toPath string) error
 46	ReplaceContent     func(fromFs model.FsBase, filepath string, oldContent string, content string) error
 47	WriteContent       func(fromFs model.FsBase, filepath string, content string) error
 48	CommitAllIfNeeded  func(message string)
 49	DiffWithParent     func(hash string, oldFilepath string, newFilepath string) (string, error)
 50	Merge              func(from string, to string)
 51	Commits            func(from string, to string) ([]model.Commit, error)
 52	Exec               func(exec model.Exec) (model.ExecStatus, error)
 53	Report             func(level model.ReportLevel, content []string) error
 54	CanCallFunc        func(plugin string, name string, args map[string]string) bool
 55	ExportFunc         func(name string, callback func(args map[string]string) (map[string]string, error))
 56	CallFunc           func(plugin string, name string, args map[string]string) (map[string]string, error)
 57}
 58
 59type nbCall struct {
 60	PluginOption       int
 61	ForgeConf          int
 62	Worktree           int
 63	Webcontent         int
 64	Cache              int
 65	ModifyContent      int
 66	ModifyWebContent   int
 67	ReplaceWebContent  int
 68	ModifyCacheContent int
 69	CopyFile           int
 70	DeleteFile         int
 71	MoveFile           int
 72	ReplaceContent     int
 73	WriteContent       int
 74	CommitAllIfNeeded  int
 75	DiffWithParent     int
 76	Log                int
 77	LogError           int
 78	Merge              int
 79	Commits            int
 80	Exec               int
 81	Report             int
 82	CanCallFunc        int
 83	ExportFunc         int
 84	CallFunc           int
 85}
 86
 87func NewFakeServer(t *testing.T) *fakeServer {
 88	dirGit, _ := os.MkdirTemp("", "gitrootPluginTestGit*")
 89	dirWeb, _ := os.MkdirTemp("", "gitrootPluginTestWeb*")
 90	dirCache, _ := os.MkdirTemp("", "gitrootPluginTestCache*")
 91	s := &fakeServer{
 92		t:         t,
 93		dirGit:    dirGit,
 94		fsGit:     model.FsBase(dirGit),
 95		grfsGit:   nil,
 96		dirWeb:    dirWeb,
 97		fsWeb:     model.FsBase(dirWeb),
 98		grfsWeb:   nil,
 99		dirCache:  dirCache,
100		fsCache:   model.FsBase(dirCache),
101		grfsCache: nil,
102		override:  Override{},
103		NbCall:    nbCall{},
104	}
105	s.grfsGit = model.NewGrFs(s.fsGit, s)
106	s.grfsWeb = model.NewGrFs(s.fsWeb, s)
107	s.grfsCache = model.NewGrFs(s.fsCache, s)
108	return s
109}
110
111func NewFakeServerWithOverride(t *testing.T, o Override) *fakeServer {
112	return NewFakeServer(t).SetOverride(o)
113}
114
115func (s *fakeServer) SetOverride(o Override) *fakeServer {
116	s.override = o
117	return s
118}
119
120// To check that interface is always respected
121func (s *fakeServer) AsServer() model.Server {
122	return s
123}
124
125func (s *fakeServer) PluginOption(opts ...model.PluginOptionWith) {
126	s.NbCall.PluginOption++
127	if s.override.PluginOption != nil {
128		s.override.PluginOption(opts...)
129	}
130}
131
132func (s *fakeServer) ForgeConf() (model.ForgeConf, error) {
133	s.NbCall.ForgeConf++
134	if s.override.ForgeConf != nil {
135		return s.override.ForgeConf()
136	}
137	return model.ForgeConf{
138		Domain:             "test",
139		ExternalSshAddr:    "ssh://127.0.0.1:4545/",
140		ExternalHttpAddr:   "http://127.0.0.1:4546/",
141		RootRepositoryName: "root",
142	}, nil
143}
144
145func (s *fakeServer) Worktree() *model.GrFs {
146	s.NbCall.Worktree++
147	if s.override.Worktree != nil {
148		return s.override.Worktree()
149	}
150	return s.grfsGit
151}
152
153func (s *fakeServer) Webcontent() *model.GrFs {
154	s.NbCall.Webcontent++
155	if s.override.Webcontent != nil {
156		return s.override.Webcontent()
157	}
158	return s.grfsWeb
159}
160
161func (s *fakeServer) Cache() *model.GrFs {
162	s.NbCall.Cache++
163	if s.override.Cache != nil {
164		return s.override.Cache()
165	}
166	return s.grfsCache
167}
168
169// TODO delete after v0.4 is released
170//
171// Deprecated: Use Worktree().WriteContent(filepath string, content string) instead
172func (s *fakeServer) ModifyContent(path, content string) {
173	s.NbCall.ModifyContent++
174	if s.override.ModifyContent != nil {
175		s.override.ModifyContent(path, content)
176	} else {
177		os.MkdirAll(s.dirGit+"/"+filepath.Dir(path), fs.ModePerm)
178		if err := os.WriteFile(s.dirGit+"/"+path, []byte(content), fs.ModePerm); err != nil {
179			s.t.Fatal(err)
180		}
181	}
182}
183
184// TODO delete after v0.4 is released
185//
186// Deprecated: Use Webcontent().WriteContent(filepath string, content string) instead
187func (s *fakeServer) ModifyWebContent(path, content string) {
188	s.NbCall.ModifyWebContent++
189	s.t.Logf("ModifyWebContent called with path=%s", path)
190	if s.override.ModifyWebContent != nil {
191		s.override.ModifyWebContent(path, content)
192	} else {
193		os.MkdirAll(s.dirWeb+"/"+filepath.Dir(path), fs.ModePerm)
194		if err := os.WriteFile(s.dirWeb+"/"+path, []byte(content), fs.ModePerm); err != nil {
195			s.t.Fatal(err)
196		}
197	}
198}
199
200// TODO delete after v0.4 is released
201//
202// Deprecated: Use Webcontent().ReplaceContent(filepath string, oldContent string, content string) instead
203func (s *fakeServer) ReplaceWebContent(filepath, oldContent, content string) {
204	s.NbCall.ReplaceWebContent++
205	if s.override.ReplaceWebContent != nil {
206		s.override.ReplaceWebContent(filepath, oldContent, content)
207	} else {
208		content, err := os.ReadFile(filepath)
209		if err != nil {
210			s.t.Fatal(err)
211		}
212
213		if err := os.WriteFile(s.dirWeb+"/"+filepath, bytes.Replace(content, []byte(oldContent), []byte(content), 1), 0666); err != nil {
214			s.t.Fatal(err)
215		}
216	}
217}
218
219// TODO delete after v0.4 is released
220//
221// Deprecated: Use Cache().WriteContent(filepath string, content string) instead
222func (s *fakeServer) ModifyCacheContent(path, content string) {
223	s.NbCall.ModifyCacheContent++
224	if s.override.ModifyCacheContent != nil {
225		s.override.ModifyCacheContent(path, content)
226	} else {
227		os.MkdirAll(s.dirCache+"/"+filepath.Dir(path), fs.ModePerm)
228		if err := os.WriteFile(s.dirCache+"/"+path, []byte(content), fs.ModePerm); err != nil {
229			s.t.Fatal(err)
230		}
231	}
232}
233
234// CopyFile implements [model.ServerNeeded].
235func (s *fakeServer) CopyFile(fromFs model.FsBase, fromPath string, toFs model.FsBase, toPath string) error {
236	s.NbCall.CopyFile++
237	if s.override.CopyFile != nil {
238		return s.override.CopyFile(fromFs, fromPath, toFs, toPath)
239	} else {
240		realPath := ""
241		switch fromFs {
242		case s.fsGit:
243			realPath = filepath.Join(s.dirGit, fromPath)
244		case s.fsWeb:
245			realPath = filepath.Join(s.dirWeb, fromPath)
246		case s.fsCache:
247			realPath = filepath.Join(s.dirCache, fromPath)
248		default:
249			return errors.New("unknown fsbase")
250		}
251		content, err := os.ReadFile(realPath)
252		if err != nil {
253			return err
254		}
255		return s.WriteContent(toFs, toPath, string(content))
256	}
257}
258
259// DeleteFile implements [model.ServerNeeded].
260func (s *fakeServer) DeleteFile(fromFs model.FsBase, filename string) error {
261	s.NbCall.DeleteFile++
262	if s.override.DeleteFile != nil {
263		return s.override.DeleteFile(fromFs, filename)
264	} else {
265		realPath := ""
266		switch fromFs {
267		case s.fsGit:
268			realPath = filepath.Join(s.dirGit, filename)
269		case s.fsWeb:
270			realPath = filepath.Join(s.dirWeb, filename)
271		case s.fsCache:
272			realPath = filepath.Join(s.dirCache, filename)
273		default:
274			return errors.New("unknown fsbase")
275		}
276		return os.Remove(realPath)
277	}
278}
279
280// MoveFile implements [model.ServerNeeded].
281func (s *fakeServer) MoveFile(fromFs model.FsBase, fromPath string, toFs model.FsBase, toPath string) error {
282	s.NbCall.MoveFile++
283	if s.override.MoveFile != nil {
284		return s.override.MoveFile(fromFs, fromPath, toFs, toPath)
285	} else {
286		if err := s.CopyFile(fromFs, fromPath, toFs, toPath); err != nil {
287			return err
288		}
289		return s.DeleteFile(fromFs, fromPath)
290	}
291}
292
293// ReplaceContent implements [model.ServerNeeded].
294func (s *fakeServer) ReplaceContent(fromFs model.FsBase, path string, oldContent string, content string) error {
295	s.NbCall.ReplaceContent++
296	if s.override.ReplaceContent != nil {
297		return s.override.ReplaceContent(fromFs, path, oldContent, content)
298	} else {
299		realPath := ""
300		switch fromFs {
301		case s.fsGit:
302			realPath = filepath.Join(s.dirGit, path)
303		case s.fsWeb:
304			realPath = filepath.Join(s.dirWeb, path)
305		case s.fsCache:
306			realPath = filepath.Join(s.dirCache, path)
307		default:
308			return errors.New("unknown fsbase")
309		}
310		content, err := os.ReadFile(realPath)
311		if err != nil {
312			return err
313		}
314		return os.WriteFile(realPath, bytes.Replace(content, []byte(oldContent), []byte(content), 1), 0666)
315	}
316}
317
318// WriteContent implements [model.ServerNeeded].
319func (s *fakeServer) WriteContent(fromFs model.FsBase, path string, content string) error {
320	s.NbCall.WriteContent++
321	if s.override.WriteContent != nil {
322		return s.override.WriteContent(fromFs, path, content)
323	} else {
324		switch fromFs {
325		case s.fsGit:
326			os.MkdirAll(s.dirGit+"/"+filepath.Dir(path), fs.ModePerm)
327			return os.WriteFile(s.dirGit+"/"+path, []byte(content), fs.ModePerm)
328		case s.fsWeb:
329			os.MkdirAll(s.dirWeb+"/"+filepath.Dir(path), fs.ModePerm)
330			return os.WriteFile(s.dirWeb+"/"+path, []byte(content), fs.ModePerm)
331		case s.fsCache:
332			os.MkdirAll(s.dirCache+"/"+filepath.Dir(path), fs.ModePerm)
333			return os.WriteFile(s.dirCache+"/"+path, []byte(content), fs.ModePerm)
334		}
335		return errors.New("unknown fsbase")
336	}
337}
338
339func (s *fakeServer) CommitAllIfNeeded(message string) {
340	s.NbCall.CommitAllIfNeeded++
341	if s.override.CommitAllIfNeeded != nil {
342		s.override.CommitAllIfNeeded(message)
343	}
344}
345
346func (s *fakeServer) DiffWithParent(hash string, oldFilepath string, newFilepath string) (string, error) {
347	s.NbCall.DiffWithParent++
348	if s.override.DiffWithParent != nil {
349		return s.override.DiffWithParent(hash, oldFilepath, newFilepath)
350	}
351	return "", nil
352}
353
354func (s *fakeServer) Log(message string) {
355	s.NbCall.Log++
356}
357
358func (s *fakeServer) LogError(message string, err error) {
359	s.NbCall.LogError++
360}
361
362func (s *fakeServer) Merge(from string, to string) {
363	s.NbCall.Merge++
364	if s.override.Merge != nil {
365		s.override.Merge(from, to)
366	}
367}
368
369func (s *fakeServer) Commits(from string, to string) ([]model.Commit, error) {
370	s.NbCall.Commits++
371	if s.override.Commits != nil {
372		return s.override.Commits(from, to)
373	}
374	return []model.Commit{}, nil
375}
376
377func (s *fakeServer) Exec(exec model.Exec) (model.ExecStatus, error) {
378	s.NbCall.Exec++
379	if s.override.Exec != nil {
380		return s.override.Exec(exec)
381	}
382	return model.ExecStatus{}, nil
383}
384
385func (s *fakeServer) Report(level model.ReportLevel, content []string) error {
386	s.NbCall.Report++
387	if s.override.Report != nil {
388		return s.override.Report(level, content)
389	}
390	return nil
391}
392
393func (s *fakeServer) ExportFunc(name string, callback func(args map[string]string) (map[string]string, error)) {
394	s.NbCall.ExportFunc++
395	if s.override.ExportFunc != nil {
396		s.override.ExportFunc(name, callback)
397	}
398}
399
400func (s *fakeServer) CanCallFunc(plugin string, name string, args map[string]string) bool {
401	s.NbCall.CanCallFunc++
402	if s.override.CanCallFunc != nil {
403		return s.override.CanCallFunc(plugin, name, args)
404	}
405	return false
406}
407
408func (s *fakeServer) CallFunc(plugin string, name string, args map[string]string) (map[string]string, error) {
409	s.NbCall.CallFunc++
410	if s.override.CallFunc != nil {
411		return s.override.CallFunc(plugin, name, args)
412	}
413	return map[string]string{}, nil
414}