Refactor core tests and improve mock implementations

Signed-off-by: Snider <snider@lt.hn>
This commit is contained in:
Snider 2025-11-04 13:12:09 +00:00
parent d25bd5c5ca
commit 5d4e081143
12 changed files with 443 additions and 2193 deletions

View file

@ -18,5 +18,4 @@ jobs:
with: with:
python-version: 3.x python-version: 3.x
- run: pip install mkdocs-material - run: pip install mkdocs-material
- run: go run ./cmd/core dev docgen
- run: mkdocs gh-deploy --force - run: mkdocs gh-deploy --force

View file

@ -42,13 +42,3 @@ tasks:
cmds: cmds:
- task: cov - task: cov
- go tool cover -html=coverage.txt - go tool cover -html=coverage.txt
sync:
desc: "Updates the public API Go files to match the exported interface of the modules."
cmds:
- task: cli:sync
test-gen:
desc: "Generates tests for the public API."
cmds:
- task: cli:test-gen

View file

@ -19,155 +19,36 @@ The key features of this package are:
- Consistent error handling: Encourages a uniform approach to error - Consistent error handling: Encourages a uniform approach to error
handling across the entire codebase. handling across the entire codebase.
## Types ## Types
### `type Error` ### `type Error`
`Error` represents a standardized error with operational context.
```go ```go
type Error 0 *ast.StructType { type Error struct {
1 . Struct: - // Op is the operation being performed, e.g., "config.Load".
2 . Fields: *ast.FieldList { Op string
3 . . Opening: - // Msg is a human-readable message explaining the error.
4 . . List: []*ast.Field (len = 3) { Msg string
5 . . . 0: *ast.Field { // Err is the underlying error that was wrapped.
6 . . . . Doc: *ast.CommentGroup { Err error
7 . . . . . List: []*ast.Comment (len = 1) { }
8 . . . . . . 0: *ast.Comment {
9 . . . . . . . Slash: -
10 . . . . . . . Text: "// Op is the operation being performed, e.g., \"config.Load\"."
11 . . . . . . }
12 . . . . . }
13 . . . . }
14 . . . . Names: []*ast.Ident (len = 1) {
15 . . . . . 0: *ast.Ident {
16 . . . . . . NamePos: -
17 . . . . . . Name: "Op"
18 . . . . . . Obj: *ast.Object {
19 . . . . . . . Kind: var
20 . . . . . . . Name: "Op"
21 . . . . . . . Decl: *(obj @ 5)
22 . . . . . . . Data: nil
23 . . . . . . . Type: nil
24 . . . . . . }
25 . . . . . }
26 . . . . }
27 . . . . Type: *ast.Ident {
28 . . . . . NamePos: -
29 . . . . . Name: "string"
30 . . . . . Obj: nil
31 . . . . }
32 . . . . Tag: nil
33 . . . . Comment: nil
34 . . . }
35 . . . 1: *ast.Field {
36 . . . . Doc: *ast.CommentGroup {
37 . . . . . List: []*ast.Comment (len = 1) {
38 . . . . . . 0: *ast.Comment {
39 . . . . . . . Slash: -
40 . . . . . . . Text: "// Msg is a human-readable message explaining the error."
41 . . . . . . }
42 . . . . . }
43 . . . . }
44 . . . . Names: []*ast.Ident (len = 1) {
45 . . . . . 0: *ast.Ident {
46 . . . . . . NamePos: -
47 . . . . . . Name: "Msg"
48 . . . . . . Obj: *ast.Object {
49 . . . . . . . Kind: var
50 . . . . . . . Name: "Msg"
51 . . . . . . . Decl: *(obj @ 35)
52 . . . . . . . Data: nil
53 . . . . . . . Type: nil
54 . . . . . . }
55 . . . . . }
56 . . . . }
57 . . . . Type: *ast.Ident {
58 . . . . . NamePos: -
59 . . . . . Name: "string"
60 . . . . . Obj: nil
61 . . . . }
62 . . . . Tag: nil
63 . . . . Comment: nil
64 . . . }
65 . . . 2: *ast.Field {
66 . . . . Doc: *ast.CommentGroup {
67 . . . . . List: []*ast.Comment (len = 1) {
68 . . . . . . 0: *ast.Comment {
69 . . . . . . . Slash: -
70 . . . . . . . Text: "// Err is the underlying error that was wrapped."
71 . . . . . . }
72 . . . . . }
73 . . . . }
74 . . . . Names: []*ast.Ident (len = 1) {
75 . . . . . 0: *ast.Ident {
76 . . . . . . NamePos: -
77 . . . . . . Name: "Err"
78 . . . . . . Obj: *ast.Object {
79 . . . . . . . Kind: var
80 . . . . . . . Name: "Err"
81 . . . . . . . Decl: *(obj @ 65)
82 . . . . . . . Data: nil
83 . . . . . . . Type: nil
84 . . . . . . }
85 . . . . . }
86 . . . . }
87 . . . . Type: *ast.Ident {
88 . . . . . NamePos: -
89 . . . . . Name: "error"
90 . . . . . Obj: nil
91 . . . . }
92 . . . . Tag: nil
93 . . . . Comment: nil
94 . . . }
95 . . }
96 . . Closing: -
97 . }
98 . Incomplete: false
99 }
``` ```
Error represents a standardized error with operational context.
#### Methods #### Methods
- `Error() 0 *ast.Ident { - `Error() string`: Error returns the string representation of the error.
1 . NamePos: - - `Unwrap() error`: Unwrap provides compatibility for Go's errors.Is and errors.As functions.
2 . Name: "string"
3 . Obj: nil
4 }
`: Error returns the string representation of the error.
- `Unwrap() 0 *ast.Ident {
1 . NamePos: -
2 . Name: "error"
3 . Obj: nil
4 }
`: Unwrap provides compatibility for Go's errors.Is and errors.As functions.
## Functions ## Functions
- `E(op, msg 0 *ast.Ident { - `E(op, msg string, err error) error`: E is a helper function to create a new Error.
1 . NamePos: -
2 . Name: "string" This is the primary way to create errors that will be consumed by the system. For example:
3 . Obj: nil
4 } ```go
, err 0 *ast.Ident { return e.E("config.Load", "failed to load config file", err)
1 . NamePos: - ```
2 . Name: "error"
3 . Obj: nil The `op` parameter should be in the format of `package.function` or `service.method`. The `msg` parameter should be a human-readable message that can be displayed to the user. The `err` parameter is the underlying error that is being wrapped.
4 }
) 0 *ast.Ident {
1 . NamePos: -
2 . Name: "error"
3 . Obj: nil
4 }
`: E is a helper function to create a new Error. This is the primary way to create errors that will be consumed by the system. For example: return e.E("config.Load", "failed to load config file", err) The 'op' parameter should be in the format of 'package.function' or 'service.method'. The 'msg' parameter should be a human-readable message that can be displayed to the user. The 'err' parameter is the underlying error that is being wrapped.

View file

@ -3,795 +3,62 @@ title: io
--- ---
# Service: `io` # Service: `io`
The `io` service provides a standardized interface for interacting with different storage backends, such as the local disk, S3, or SFTP.
## Types ## Types
### `type Medium` ### `type Medium`
`Medium` defines the standard interface for a storage backend.
```go ```go
type Medium 0 *ast.InterfaceType { type Medium interface {
1 . Interface: - // Read retrieves the content of a file as a string.
2 . Methods: *ast.FieldList { Read(path string) (string, error)
3 . . Opening: -
4 . . List: []*ast.Field (len = 6) {
5 . . . 0: *ast.Field {
6 . . . . Doc: *ast.CommentGroup {
7 . . . . . List: []*ast.Comment (len = 1) {
8 . . . . . . 0: *ast.Comment {
9 . . . . . . . Slash: -
10 . . . . . . . Text: "// Read retrieves the content of a file as a string."
11 . . . . . . }
12 . . . . . }
13 . . . . }
14 . . . . Names: []*ast.Ident (len = 1) {
15 . . . . . 0: *ast.Ident {
16 . . . . . . NamePos: -
17 . . . . . . Name: "Read"
18 . . . . . . Obj: *ast.Object {
19 . . . . . . . Kind: func
20 . . . . . . . Name: "Read"
21 . . . . . . . Decl: *(obj @ 5)
22 . . . . . . . Data: nil
23 . . . . . . . Type: nil
24 . . . . . . }
25 . . . . . }
26 . . . . }
27 . . . . Type: *ast.FuncType {
28 . . . . . Func: -
29 . . . . . TypeParams: nil
30 . . . . . Params: *ast.FieldList {
31 . . . . . . Opening: -
32 . . . . . . List: []*ast.Field (len = 1) {
33 . . . . . . . 0: *ast.Field {
34 . . . . . . . . Doc: nil
35 . . . . . . . . Names: []*ast.Ident (len = 1) {
36 . . . . . . . . . 0: *ast.Ident {
37 . . . . . . . . . . NamePos: -
38 . . . . . . . . . . Name: "path"
39 . . . . . . . . . . Obj: *ast.Object {
40 . . . . . . . . . . . Kind: var
41 . . . . . . . . . . . Name: "path"
42 . . . . . . . . . . . Decl: *(obj @ 33)
43 . . . . . . . . . . . Data: nil
44 . . . . . . . . . . . Type: nil
45 . . . . . . . . . . }
46 . . . . . . . . . }
47 . . . . . . . . }
48 . . . . . . . . Type: *ast.Ident {
49 . . . . . . . . . NamePos: -
50 . . . . . . . . . Name: "string"
51 . . . . . . . . . Obj: nil
52 . . . . . . . . }
53 . . . . . . . . Tag: nil
54 . . . . . . . . Comment: nil
55 . . . . . . . }
56 . . . . . . }
57 . . . . . . Closing: -
58 . . . . . }
59 . . . . . Results: *ast.FieldList {
60 . . . . . . Opening: -
61 . . . . . . List: []*ast.Field (len = 2) {
62 . . . . . . . 0: *ast.Field {
63 . . . . . . . . Doc: nil
64 . . . . . . . . Names: nil
65 . . . . . . . . Type: *ast.Ident {
66 . . . . . . . . . NamePos: -
67 . . . . . . . . . Name: "string"
68 . . . . . . . . . Obj: nil
69 . . . . . . . . }
70 . . . . . . . . Tag: nil
71 . . . . . . . . Comment: nil
72 . . . . . . . }
73 . . . . . . . 1: *ast.Field {
74 . . . . . . . . Doc: nil
75 . . . . . . . . Names: nil
76 . . . . . . . . Type: *ast.Ident {
77 . . . . . . . . . NamePos: -
78 . . . . . . . . . Name: "error"
79 . . . . . . . . . Obj: nil
80 . . . . . . . . }
81 . . . . . . . . Tag: nil
82 . . . . . . . . Comment: nil
83 . . . . . . . }
84 . . . . . . }
85 . . . . . . Closing: -
86 . . . . . }
87 . . . . }
88 . . . . Tag: nil
89 . . . . Comment: nil
90 . . . }
91 . . . 1: *ast.Field {
92 . . . . Doc: *ast.CommentGroup {
93 . . . . . List: []*ast.Comment (len = 1) {
94 . . . . . . 0: *ast.Comment {
95 . . . . . . . Slash: -
96 . . . . . . . Text: "// Write saves the given content to a file, overwriting it if it exists."
97 . . . . . . }
98 . . . . . }
99 . . . . }
100 . . . . Names: []*ast.Ident (len = 1) {
101 . . . . . 0: *ast.Ident {
102 . . . . . . NamePos: -
103 . . . . . . Name: "Write"
104 . . . . . . Obj: *ast.Object {
105 . . . . . . . Kind: func
106 . . . . . . . Name: "Write"
107 . . . . . . . Decl: *(obj @ 91)
108 . . . . . . . Data: nil
109 . . . . . . . Type: nil
110 . . . . . . }
111 . . . . . }
112 . . . . }
113 . . . . Type: *ast.FuncType {
114 . . . . . Func: -
115 . . . . . TypeParams: nil
116 . . . . . Params: *ast.FieldList {
117 . . . . . . Opening: -
118 . . . . . . List: []*ast.Field (len = 1) {
119 . . . . . . . 0: *ast.Field {
120 . . . . . . . . Doc: nil
121 . . . . . . . . Names: []*ast.Ident (len = 2) {
122 . . . . . . . . . 0: *ast.Ident {
123 . . . . . . . . . . NamePos: -
124 . . . . . . . . . . Name: "path"
125 . . . . . . . . . . Obj: *ast.Object {
126 . . . . . . . . . . . Kind: var
127 . . . . . . . . . . . Name: "path"
128 . . . . . . . . . . . Decl: *(obj @ 119)
129 . . . . . . . . . . . Data: nil
130 . . . . . . . . . . . Type: nil
131 . . . . . . . . . . }
132 . . . . . . . . . }
133 . . . . . . . . . 1: *ast.Ident {
134 . . . . . . . . . . NamePos: -
135 . . . . . . . . . . Name: "content"
136 . . . . . . . . . . Obj: *ast.Object {
137 . . . . . . . . . . . Kind: var
138 . . . . . . . . . . . Name: "content"
139 . . . . . . . . . . . Decl: *(obj @ 119)
140 . . . . . . . . . . . Data: nil
141 . . . . . . . . . . . Type: nil
142 . . . . . . . . . . }
143 . . . . . . . . . }
144 . . . . . . . . }
145 . . . . . . . . Type: *ast.Ident {
146 . . . . . . . . . NamePos: -
147 . . . . . . . . . Name: "string"
148 . . . . . . . . . Obj: nil
149 . . . . . . . . }
150 . . . . . . . . Tag: nil
151 . . . . . . . . Comment: nil
152 . . . . . . . }
153 . . . . . . }
154 . . . . . . Closing: -
155 . . . . . }
156 . . . . . Results: *ast.FieldList {
157 . . . . . . Opening: -
158 . . . . . . List: []*ast.Field (len = 1) {
159 . . . . . . . 0: *ast.Field {
160 . . . . . . . . Doc: nil
161 . . . . . . . . Names: nil
162 . . . . . . . . Type: *ast.Ident {
163 . . . . . . . . . NamePos: -
164 . . . . . . . . . Name: "error"
165 . . . . . . . . . Obj: nil
166 . . . . . . . . }
167 . . . . . . . . Tag: nil
168 . . . . . . . . Comment: nil
169 . . . . . . . }
170 . . . . . . }
171 . . . . . . Closing: -
172 . . . . . }
173 . . . . }
174 . . . . Tag: nil
175 . . . . Comment: nil
176 . . . }
177 . . . 2: *ast.Field {
178 . . . . Doc: *ast.CommentGroup {
179 . . . . . List: []*ast.Comment (len = 1) {
180 . . . . . . 0: *ast.Comment {
181 . . . . . . . Slash: -
182 . . . . . . . Text: "// EnsureDir makes sure a directory exists, creating it if necessary."
183 . . . . . . }
184 . . . . . }
185 . . . . }
186 . . . . Names: []*ast.Ident (len = 1) {
187 . . . . . 0: *ast.Ident {
188 . . . . . . NamePos: -
189 . . . . . . Name: "EnsureDir"
190 . . . . . . Obj: *ast.Object {
191 . . . . . . . Kind: func
192 . . . . . . . Name: "EnsureDir"
193 . . . . . . . Decl: *(obj @ 177)
194 . . . . . . . Data: nil
195 . . . . . . . Type: nil
196 . . . . . . }
197 . . . . . }
198 . . . . }
199 . . . . Type: *ast.FuncType {
200 . . . . . Func: -
201 . . . . . TypeParams: nil
202 . . . . . Params: *ast.FieldList {
203 . . . . . . Opening: -
204 . . . . . . List: []*ast.Field (len = 1) {
205 . . . . . . . 0: *ast.Field {
206 . . . . . . . . Doc: nil
207 . . . . . . . . Names: []*ast.Ident (len = 1) {
208 . . . . . . . . . 0: *ast.Ident {
209 . . . . . . . . . . NamePos: -
210 . . . . . . . . . . Name: "path"
211 . . . . . . . . . . Obj: *ast.Object {
212 . . . . . . . . . . . Kind: var
213 . . . . . . . . . . . Name: "path"
214 . . . . . . . . . . . Decl: *(obj @ 205)
215 . . . . . . . . . . . Data: nil
216 . . . . . . . . . . . Type: nil
217 . . . . . . . . . . }
218 . . . . . . . . . }
219 . . . . . . . . }
220 . . . . . . . . Type: *ast.Ident {
221 . . . . . . . . . NamePos: -
222 . . . . . . . . . Name: "string"
223 . . . . . . . . . Obj: nil
224 . . . . . . . . }
225 . . . . . . . . Tag: nil
226 . . . . . . . . Comment: nil
227 . . . . . . . }
228 . . . . . . }
229 . . . . . . Closing: -
230 . . . . . }
231 . . . . . Results: *ast.FieldList {
232 . . . . . . Opening: -
233 . . . . . . List: []*ast.Field (len = 1) {
234 . . . . . . . 0: *ast.Field {
235 . . . . . . . . Doc: nil
236 . . . . . . . . Names: nil
237 . . . . . . . . Type: *ast.Ident {
238 . . . . . . . . . NamePos: -
239 . . . . . . . . . Name: "error"
240 . . . . . . . . . Obj: nil
241 . . . . . . . . }
242 . . . . . . . . Tag: nil
243 . . . . . . . . Comment: nil
244 . . . . . . . }
245 . . . . . . }
246 . . . . . . Closing: -
247 . . . . . }
248 . . . . }
249 . . . . Tag: nil
250 . . . . Comment: nil
251 . . . }
252 . . . 3: *ast.Field {
253 . . . . Doc: *ast.CommentGroup {
254 . . . . . List: []*ast.Comment (len = 1) {
255 . . . . . . 0: *ast.Comment {
256 . . . . . . . Slash: -
257 . . . . . . . Text: "// IsFile checks if a path exists and is a regular file."
258 . . . . . . }
259 . . . . . }
260 . . . . }
261 . . . . Names: []*ast.Ident (len = 1) {
262 . . . . . 0: *ast.Ident {
263 . . . . . . NamePos: -
264 . . . . . . Name: "IsFile"
265 . . . . . . Obj: *ast.Object {
266 . . . . . . . Kind: func
267 . . . . . . . Name: "IsFile"
268 . . . . . . . Decl: *(obj @ 252)
269 . . . . . . . Data: nil
270 . . . . . . . Type: nil
271 . . . . . . }
272 . . . . . }
273 . . . . }
274 . . . . Type: *ast.FuncType {
275 . . . . . Func: -
276 . . . . . TypeParams: nil
277 . . . . . Params: *ast.FieldList {
278 . . . . . . Opening: -
279 . . . . . . List: []*ast.Field (len = 1) {
280 . . . . . . . 0: *ast.Field {
281 . . . . . . . . Doc: nil
282 . . . . . . . . Names: []*ast.Ident (len = 1) {
283 . . . . . . . . . 0: *ast.Ident {
284 . . . . . . . . . . NamePos: -
285 . . . . . . . . . . Name: "path"
286 . . . . . . . . . . Obj: *ast.Object {
287 . . . . . . . . . . . Kind: var
288 . . . . . . . . . . . Name: "path"
289 . . . . . . . . . . . Decl: *(obj @ 280)
290 . . . . . . . . . . . Data: nil
291 . . . . . . . . . . . Type: nil
292 . . . . . . . . . . }
293 . . . . . . . . . }
294 . . . . . . . . }
295 . . . . . . . . Type: *ast.Ident {
296 . . . . . . . . . NamePos: -
297 . . . . . . . . . Name: "string"
298 . . . . . . . . . Obj: nil
299 . . . . . . . . }
300 . . . . . . . . Tag: nil
301 . . . . . . . . Comment: nil
302 . . . . . . . }
303 . . . . . . }
304 . . . . . . Closing: -
305 . . . . . }
306 . . . . . Results: *ast.FieldList {
307 . . . . . . Opening: -
308 . . . . . . List: []*ast.Field (len = 1) {
309 . . . . . . . 0: *ast.Field {
310 . . . . . . . . Doc: nil
311 . . . . . . . . Names: nil
312 . . . . . . . . Type: *ast.Ident {
313 . . . . . . . . . NamePos: -
314 . . . . . . . . . Name: "bool"
315 . . . . . . . . . Obj: nil
316 . . . . . . . . }
317 . . . . . . . . Tag: nil
318 . . . . . . . . Comment: nil
319 . . . . . . . }
320 . . . . . . }
321 . . . . . . Closing: -
322 . . . . . }
323 . . . . }
324 . . . . Tag: nil
325 . . . . Comment: nil
326 . . . }
327 . . . 4: *ast.Field {
328 . . . . Doc: *ast.CommentGroup {
329 . . . . . List: []*ast.Comment (len = 1) {
330 . . . . . . 0: *ast.Comment {
331 . . . . . . . Slash: -
332 . . . . . . . Text: "// FileGet is a convenience function that reads a file from the medium."
333 . . . . . . }
334 . . . . . }
335 . . . . }
336 . . . . Names: []*ast.Ident (len = 1) {
337 . . . . . 0: *ast.Ident {
338 . . . . . . NamePos: -
339 . . . . . . Name: "FileGet"
340 . . . . . . Obj: *ast.Object {
341 . . . . . . . Kind: func
342 . . . . . . . Name: "FileGet"
343 . . . . . . . Decl: *(obj @ 327)
344 . . . . . . . Data: nil
345 . . . . . . . Type: nil
346 . . . . . . }
347 . . . . . }
348 . . . . }
349 . . . . Type: *ast.FuncType {
350 . . . . . Func: -
351 . . . . . TypeParams: nil
352 . . . . . Params: *ast.FieldList {
353 . . . . . . Opening: -
354 . . . . . . List: []*ast.Field (len = 1) {
355 . . . . . . . 0: *ast.Field {
356 . . . . . . . . Doc: nil
357 . . . . . . . . Names: []*ast.Ident (len = 1) {
358 . . . . . . . . . 0: *ast.Ident {
359 . . . . . . . . . . NamePos: -
360 . . . . . . . . . . Name: "path"
361 . . . . . . . . . . Obj: *ast.Object {
362 . . . . . . . . . . . Kind: var
363 . . . . . . . . . . . Name: "path"
364 . . . . . . . . . . . Decl: *(obj @ 355)
365 . . . . . . . . . . . Data: nil
366 . . . . . . . . . . . Type: nil
367 . . . . . . . . . . }
368 . . . . . . . . . }
369 . . . . . . . . }
370 . . . . . . . . Type: *ast.Ident {
371 . . . . . . . . . NamePos: -
372 . . . . . . . . . Name: "string"
373 . . . . . . . . . Obj: nil
374 . . . . . . . . }
375 . . . . . . . . Tag: nil
376 . . . . . . . . Comment: nil
377 . . . . . . . }
378 . . . . . . }
379 . . . . . . Closing: -
380 . . . . . }
381 . . . . . Results: *ast.FieldList {
382 . . . . . . Opening: -
383 . . . . . . List: []*ast.Field (len = 2) {
384 . . . . . . . 0: *ast.Field {
385 . . . . . . . . Doc: nil
386 . . . . . . . . Names: nil
387 . . . . . . . . Type: *ast.Ident {
388 . . . . . . . . . NamePos: -
389 . . . . . . . . . Name: "string"
390 . . . . . . . . . Obj: nil
391 . . . . . . . . }
392 . . . . . . . . Tag: nil
393 . . . . . . . . Comment: nil
394 . . . . . . . }
395 . . . . . . . 1: *ast.Field {
396 . . . . . . . . Doc: nil
397 . . . . . . . . Names: nil
398 . . . . . . . . Type: *ast.Ident {
399 . . . . . . . . . NamePos: -
400 . . . . . . . . . Name: "error"
401 . . . . . . . . . Obj: nil
402 . . . . . . . . }
403 . . . . . . . . Tag: nil
404 . . . . . . . . Comment: nil
405 . . . . . . . }
406 . . . . . . }
407 . . . . . . Closing: -
408 . . . . . }
409 . . . . }
410 . . . . Tag: nil
411 . . . . Comment: nil
412 . . . }
413 . . . 5: *ast.Field {
414 . . . . Doc: *ast.CommentGroup {
415 . . . . . List: []*ast.Comment (len = 1) {
416 . . . . . . 0: *ast.Comment {
417 . . . . . . . Slash: -
418 . . . . . . . Text: "// FileSet is a convenience function that writes a file to the medium."
419 . . . . . . }
420 . . . . . }
421 . . . . }
422 . . . . Names: []*ast.Ident (len = 1) {
423 . . . . . 0: *ast.Ident {
424 . . . . . . NamePos: -
425 . . . . . . Name: "FileSet"
426 . . . . . . Obj: *ast.Object {
427 . . . . . . . Kind: func
428 . . . . . . . Name: "FileSet"
429 . . . . . . . Decl: *(obj @ 413)
430 . . . . . . . Data: nil
431 . . . . . . . Type: nil
432 . . . . . . }
433 . . . . . }
434 . . . . }
435 . . . . Type: *ast.FuncType {
436 . . . . . Func: -
437 . . . . . TypeParams: nil
438 . . . . . Params: *ast.FieldList {
439 . . . . . . Opening: -
440 . . . . . . List: []*ast.Field (len = 1) {
441 . . . . . . . 0: *ast.Field {
442 . . . . . . . . Doc: nil
443 . . . . . . . . Names: []*ast.Ident (len = 2) {
444 . . . . . . . . . 0: *ast.Ident {
445 . . . . . . . . . . NamePos: -
446 . . . . . . . . . . Name: "path"
447 . . . . . . . . . . Obj: *ast.Object {
448 . . . . . . . . . . . Kind: var
449 . . . . . . . . . . . Name: "path"
450 . . . . . . . . . . . Decl: *(obj @ 441)
451 . . . . . . . . . . . Data: nil
452 . . . . . . . . . . . Type: nil
453 . . . . . . . . . . }
454 . . . . . . . . . }
455 . . . . . . . . . 1: *ast.Ident {
456 . . . . . . . . . . NamePos: -
457 . . . . . . . . . . Name: "content"
458 . . . . . . . . . . Obj: *ast.Object {
459 . . . . . . . . . . . Kind: var
460 . . . . . . . . . . . Name: "content"
461 . . . . . . . . . . . Decl: *(obj @ 441)
462 . . . . . . . . . . . Data: nil
463 . . . . . . . . . . . Type: nil
464 . . . . . . . . . . }
465 . . . . . . . . . }
466 . . . . . . . . }
467 . . . . . . . . Type: *ast.Ident {
468 . . . . . . . . . NamePos: -
469 . . . . . . . . . Name: "string"
470 . . . . . . . . . Obj: nil
471 . . . . . . . . }
472 . . . . . . . . Tag: nil
473 . . . . . . . . Comment: nil
474 . . . . . . . }
475 . . . . . . }
476 . . . . . . Closing: -
477 . . . . . }
478 . . . . . Results: *ast.FieldList {
479 . . . . . . Opening: -
480 . . . . . . List: []*ast.Field (len = 1) {
481 . . . . . . . 0: *ast.Field {
482 . . . . . . . . Doc: nil
483 . . . . . . . . Names: nil
484 . . . . . . . . Type: *ast.Ident {
485 . . . . . . . . . NamePos: -
486 . . . . . . . . . Name: "error"
487 . . . . . . . . . Obj: nil
488 . . . . . . . . }
489 . . . . . . . . Tag: nil
490 . . . . . . . . Comment: nil
491 . . . . . . . }
492 . . . . . . }
493 . . . . . . Closing: -
494 . . . . . }
495 . . . . }
496 . . . . Tag: nil
497 . . . . Comment: nil
498 . . . }
499 . . }
500 . . Closing: -
501 . }
502 . Incomplete: false
503 }
// Write saves the given content to a file, overwriting it if it exists.
Write(path, content string) error
// EnsureDir makes sure a directory exists, creating it if necessary.
EnsureDir(path string) error
// IsFile checks if a path exists and is a regular file.
IsFile(path string) bool
// FileGet is a convenience function that reads a file from the medium.
FileGet(path string) (string, error)
// FileSet is a convenience function that writes a file to the medium.
FileSet(path, content string) error
}
``` ```
Medium defines the standard interface for a storage backend.
This allows for different implementations (e.g., local disk, S3, SFTP)
to be used interchangeably.
### `type MockMedium` ### `type MockMedium`
`MockMedium` implements the `Medium` interface for testing purposes.
```go ```go
type MockMedium 0 *ast.StructType { type MockMedium struct {
1 . Struct: - Files map[string]string
2 . Fields: *ast.FieldList { Dirs map[string]bool
3 . . Opening: - }
4 . . List: []*ast.Field (len = 2) {
5 . . . 0: *ast.Field {
6 . . . . Doc: nil
7 . . . . Names: []*ast.Ident (len = 1) {
8 . . . . . 0: *ast.Ident {
9 . . . . . . NamePos: -
10 . . . . . . Name: "Files"
11 . . . . . . Obj: *ast.Object {
12 . . . . . . . Kind: var
13 . . . . . . . Name: "Files"
14 . . . . . . . Decl: *(obj @ 5)
15 . . . . . . . Data: nil
16 . . . . . . . Type: nil
17 . . . . . . }
18 . . . . . }
19 . . . . }
20 . . . . Type: *ast.MapType {
21 . . . . . Map: -
22 . . . . . Key: *ast.Ident {
23 . . . . . . NamePos: -
24 . . . . . . Name: "string"
25 . . . . . . Obj: nil
26 . . . . . }
27 . . . . . Value: *ast.Ident {
28 . . . . . . NamePos: -
29 . . . . . . Name: "string"
30 . . . . . . Obj: nil
31 . . . . . }
32 . . . . }
33 . . . . Tag: nil
34 . . . . Comment: nil
35 . . . }
36 . . . 1: *ast.Field {
37 . . . . Doc: nil
38 . . . . Names: []*ast.Ident (len = 1) {
39 . . . . . 0: *ast.Ident {
40 . . . . . . NamePos: -
41 . . . . . . Name: "Dirs"
42 . . . . . . Obj: *ast.Object {
43 . . . . . . . Kind: var
44 . . . . . . . Name: "Dirs"
45 . . . . . . . Decl: *(obj @ 36)
46 . . . . . . . Data: nil
47 . . . . . . . Type: nil
48 . . . . . . }
49 . . . . . }
50 . . . . }
51 . . . . Type: *ast.MapType {
52 . . . . . Map: -
53 . . . . . Key: *ast.Ident {
54 . . . . . . NamePos: -
55 . . . . . . Name: "string"
56 . . . . . . Obj: nil
57 . . . . . }
58 . . . . . Value: *ast.Ident {
59 . . . . . . NamePos: -
60 . . . . . . Name: "bool"
61 . . . . . . Obj: nil
62 . . . . . }
63 . . . . }
64 . . . . Tag: nil
65 . . . . Comment: nil
66 . . . }
67 . . }
68 . . Closing: -
69 . }
70 . Incomplete: false
71 }
``` ```
MockMedium implements the Medium interface for testing purposes.
#### Methods #### Methods
- `EnsureDir(path 0 *ast.Ident { - `EnsureDir(path string) error`: Mocks creating a directory.
1 . NamePos: - - `FileGet(path string) (string, error)`: Mocks reading a file.
2 . Name: "string" - `FileSet(path, content string) error`: Mocks writing a file.
3 . Obj: nil - `IsFile(path string) bool`: Mocks checking if a path is a file.
4 } - `Read(path string) (string, error)`: Mocks reading a file.
) 0 *ast.Ident { - `Write(path, content string) error`: Mocks writing a file.
1 . NamePos: -
2 . Name: "error"
3 . Obj: nil
4 }
`:
- `FileGet(path 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
) 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
, 0 *ast.Ident {
1 . NamePos: -
2 . Name: "error"
3 . Obj: nil
4 }
`:
- `FileSet(path, content 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
) 0 *ast.Ident {
1 . NamePos: -
2 . Name: "error"
3 . Obj: nil
4 }
`:
- `IsFile(path 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
) 0 *ast.Ident {
1 . NamePos: -
2 . Name: "bool"
3 . Obj: nil
4 }
`:
- `Read(path 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
) 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
, 0 *ast.Ident {
1 . NamePos: -
2 . Name: "error"
3 . Obj: nil
4 }
`:
- `Write(path, content 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
) 0 *ast.Ident {
1 . NamePos: -
2 . Name: "error"
3 . Obj: nil
4 }
`:
## Functions ## Functions
- `Copy(sourceMedium 0 *ast.Ident { - `Copy(sourceMedium Medium, sourcePath string, destMedium Medium, destPath string) error`: Copies a file from a source medium to a destination medium.
1 . NamePos: - - `EnsureDir(m Medium, path string) error`: Ensures a directory exists on the given medium.
2 . Name: "Medium" - `IsFile(m Medium, path string) bool`: Checks if a path is a file on the given medium.
3 . Obj: nil - `Read(m Medium, path string) (string, error)`: Retrieves the content of a file from the given medium.
4 } - `Write(m Medium, path, content string) error`: Saves content to a file on the given medium.
, sourcePath 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
, destMedium 0 *ast.Ident {
1 . NamePos: -
2 . Name: "Medium"
3 . Obj: nil
4 }
, destPath 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
) 0 *ast.Ident {
1 . NamePos: -
2 . Name: "error"
3 . Obj: nil
4 }
`: Copy copies a file from a source medium to a destination medium.
- `EnsureDir(m 0 *ast.Ident { Would you like to see some examples of how to use this service?
1 . NamePos: -
2 . Name: "Medium"
3 . Obj: nil
4 }
, path 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
) 0 *ast.Ident {
1 . NamePos: -
2 . Name: "error"
3 . Obj: nil
4 }
`: EnsureDir ensures a directory exists on the given medium.
- `IsFile(m 0 *ast.Ident {
1 . NamePos: -
2 . Name: "Medium"
3 . Obj: nil
4 }
, path 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
) 0 *ast.Ident {
1 . NamePos: -
2 . Name: "bool"
3 . Obj: nil
4 }
`: IsFile checks if a path is a file on the given medium.
- `Read(m 0 *ast.Ident {
1 . NamePos: -
2 . Name: "Medium"
3 . Obj: nil
4 }
, path 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
) 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
, 0 *ast.Ident {
1 . NamePos: -
2 . Name: "error"
3 . Obj: nil
4 }
`: Read retrieves the content of a file from the given medium.
- `Write(m 0 *ast.Ident {
1 . NamePos: -
2 . Name: "Medium"
3 . Obj: nil
4 }
, path, content 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
) 0 *ast.Ident {
1 . NamePos: -
2 . Name: "error"
3 . Obj: nil
4 }
`: Write saves content to a file on the given medium.

View file

@ -3,626 +3,74 @@ title: workspace
--- ---
# Service: `workspace` # Service: `workspace`
## Constants ## Constants
```godefaultWorkspacelistFile ```go
defaultWorkspacelistFile
``` ```
## Types ## Types
### `type Options` ### `type Options`
```go ```go
type Options 0 *ast.StructType { type Options struct {
1 . Struct: - // Options holds configuration for the workspace service.
2 . Fields: *ast.FieldList { }
3 . . Opening: -
4 . . List: nil
5 . . Closing: -
6 . }
7 . Incomplete: false
8 }
``` ```
Options holds configuration for the workspace service.
### `type Service` ### `type Service`
```go
type Service 0 *ast.StructType {
1 . Struct: -
2 . Fields: *ast.FieldList {
3 . . Opening: -
4 . . List: []*ast.Field (len = 4) {
5 . . . 0: *ast.Field {
6 . . . . Doc: nil
7 . . . . Names: nil
8 . . . . Type: *ast.StarExpr {
9 . . . . . Star: -
10 . . . . . X: *ast.IndexExpr {
11 . . . . . . X: *ast.SelectorExpr {
12 . . . . . . . X: *ast.Ident {
13 . . . . . . . . NamePos: -
14 . . . . . . . . Name: "core"
15 . . . . . . . . Obj: nil
16 . . . . . . . }
17 . . . . . . . Sel: *ast.Ident {
18 . . . . . . . . NamePos: -
19 . . . . . . . . Name: "Runtime"
20 . . . . . . . . Obj: nil
21 . . . . . . . }
22 . . . . . . }
23 . . . . . . Lbrack: -
24 . . . . . . Index: *ast.Ident {
25 . . . . . . . NamePos: -
26 . . . . . . . Name: "Options"
27 . . . . . . . Obj: *ast.Object {
28 . . . . . . . . Kind: type
29 . . . . . . . . Name: "Options"
30 . . . . . . . . Decl: *ast.TypeSpec {
31 . . . . . . . . . Doc: nil
32 . . . . . . . . . Name: *ast.Ident {
33 . . . . . . . . . . NamePos: -
34 . . . . . . . . . . Name: "Options"
35 . . . . . . . . . . Obj: *(obj @ 27)
36 . . . . . . . . . }
37 . . . . . . . . . TypeParams: nil
38 . . . . . . . . . Assign: -
39 . . . . . . . . . Type: *ast.StructType {
40 . . . . . . . . . . Struct: -
41 . . . . . . . . . . Fields: *ast.FieldList {
42 . . . . . . . . . . . Opening: -
43 . . . . . . . . . . . List: nil
44 . . . . . . . . . . . Closing: -
45 . . . . . . . . . . }
46 . . . . . . . . . . Incomplete: false
47 . . . . . . . . . }
48 . . . . . . . . . Comment: nil
49 . . . . . . . . }
50 . . . . . . . . Data: nil
51 . . . . . . . . Type: nil
52 . . . . . . . }
53 . . . . . . }
54 . . . . . . Rbrack: -
55 . . . . . }
56 . . . . }
57 . . . . Tag: nil
58 . . . . Comment: nil
59 . . . }
60 . . . 1: *ast.Field {
61 . . . . Doc: nil
62 . . . . Names: []*ast.Ident (len = 1) {
63 . . . . . 0: *ast.Ident {
64 . . . . . . NamePos: -
65 . . . . . . Name: "activeWorkspace"
66 . . . . . . Obj: *ast.Object {
67 . . . . . . . Kind: var
68 . . . . . . . Name: "activeWorkspace"
69 . . . . . . . Decl: *(obj @ 60)
70 . . . . . . . Data: nil
71 . . . . . . . Type: nil
72 . . . . . . }
73 . . . . . }
74 . . . . }
75 . . . . Type: *ast.StarExpr {
76 . . . . . Star: -
77 . . . . . X: *ast.Ident {
78 . . . . . . NamePos: -
79 . . . . . . Name: "Workspace"
80 . . . . . . Obj: *ast.Object {
81 . . . . . . . Kind: type
82 . . . . . . . Name: "Workspace"
83 . . . . . . . Decl: *ast.TypeSpec {
84 . . . . . . . . Doc: nil
85 . . . . . . . . Name: *ast.Ident {
86 . . . . . . . . . NamePos: -
87 . . . . . . . . . Name: "Workspace"
88 . . . . . . . . . Obj: *(obj @ 80)
89 . . . . . . . . }
90 . . . . . . . . TypeParams: nil
91 . . . . . . . . Assign: -
92 . . . . . . . . Type: *ast.StructType {
93 . . . . . . . . . Struct: -
94 . . . . . . . . . Fields: *ast.FieldList {
95 . . . . . . . . . . Opening: -
96 . . . . . . . . . . List: []*ast.Field (len = 2) {
97 . . . . . . . . . . . 0: *ast.Field {
98 . . . . . . . . . . . . Doc: nil
99 . . . . . . . . . . . . Names: []*ast.Ident (len = 1) {
100 . . . . . . . . . . . . . 0: *ast.Ident {
101 . . . . . . . . . . . . . . NamePos: -
102 . . . . . . . . . . . . . . Name: "Name"
103 . . . . . . . . . . . . . . Obj: *ast.Object {
104 . . . . . . . . . . . . . . . Kind: var
105 . . . . . . . . . . . . . . . Name: "Name"
106 . . . . . . . . . . . . . . . Decl: *(obj @ 97)
107 . . . . . . . . . . . . . . . Data: nil
108 . . . . . . . . . . . . . . . Type: nil
109 . . . . . . . . . . . . . . }
110 . . . . . . . . . . . . . }
111 . . . . . . . . . . . . }
112 . . . . . . . . . . . . Type: *ast.Ident {
113 . . . . . . . . . . . . . NamePos: -
114 . . . . . . . . . . . . . Name: "string"
115 . . . . . . . . . . . . . Obj: nil
116 . . . . . . . . . . . . }
117 . . . . . . . . . . . . Tag: nil
118 . . . . . . . . . . . . Comment: nil
119 . . . . . . . . . . . }
120 . . . . . . . . . . . 1: *ast.Field {
121 . . . . . . . . . . . . Doc: nil
122 . . . . . . . . . . . . Names: []*ast.Ident (len = 1) {
123 . . . . . . . . . . . . . 0: *ast.Ident {
124 . . . . . . . . . . . . . . NamePos: -
125 . . . . . . . . . . . . . . Name: "Path"
126 . . . . . . . . . . . . . . Obj: *ast.Object {
127 . . . . . . . . . . . . . . . Kind: var
128 . . . . . . . . . . . . . . . Name: "Path"
129 . . . . . . . . . . . . . . . Decl: *(obj @ 120)
130 . . . . . . . . . . . . . . . Data: nil
131 . . . . . . . . . . . . . . . Type: nil
132 . . . . . . . . . . . . . . }
133 . . . . . . . . . . . . . }
134 . . . . . . . . . . . . }
135 . . . . . . . . . . . . Type: *ast.Ident {
136 . . . . . . . . . . . . . NamePos: -
137 . . . . . . . . . . . . . Name: "string"
138 . . . . . . . . . . . . . Obj: nil
139 . . . . . . . . . . . . }
140 . . . . . . . . . . . . Tag: nil
141 . . . . . . . . . . . . Comment: nil
142 . . . . . . . . . . . }
143 . . . . . . . . . . }
144 . . . . . . . . . . Closing: -
145 . . . . . . . . . }
146 . . . . . . . . . Incomplete: false
147 . . . . . . . . }
148 . . . . . . . . Comment: nil
149 . . . . . . . }
150 . . . . . . . Data: nil
151 . . . . . . . Type: nil
152 . . . . . . }
153 . . . . . }
154 . . . . }
155 . . . . Tag: nil
156 . . . . Comment: nil
157 . . . }
158 . . . 2: *ast.Field {
159 . . . . Doc: nil
160 . . . . Names: []*ast.Ident (len = 1) {
161 . . . . . 0: *ast.Ident {
162 . . . . . . NamePos: -
163 . . . . . . Name: "workspaceList"
164 . . . . . . Obj: *ast.Object {
165 . . . . . . . Kind: var
166 . . . . . . . Name: "workspaceList"
167 . . . . . . . Decl: *(obj @ 158)
168 . . . . . . . Data: nil
169 . . . . . . . Type: nil
170 . . . . . . }
171 . . . . . }
172 . . . . }
173 . . . . Type: *ast.MapType {
174 . . . . . Map: -
175 . . . . . Key: *ast.Ident {
176 . . . . . . NamePos: -
177 . . . . . . Name: "string"
178 . . . . . . Obj: nil
179 . . . . . }
180 . . . . . Value: *ast.Ident {
181 . . . . . . NamePos: -
182 . . . . . . Name: "string"
183 . . . . . . Obj: nil
184 . . . . . }
185 . . . . }
186 . . . . Tag: nil
187 . . . . Comment: *ast.CommentGroup {
188 . . . . . List: []*ast.Comment (len = 1) {
189 . . . . . . 0: *ast.Comment {
190 . . . . . . . Slash: -
191 . . . . . . . Text: "// Maps Workspace ID to Public Key"
192 . . . . . . }
193 . . . . . }
194 . . . . }
195 . . . }
196 . . . 3: *ast.Field {
197 . . . . Doc: nil
198 . . . . Names: []*ast.Ident (len = 1) {
199 . . . . . 0: *ast.Ident {
200 . . . . . . NamePos: -
201 . . . . . . Name: "medium"
202 . . . . . . Obj: *ast.Object {
203 . . . . . . . Kind: var
204 . . . . . . . Name: "medium"
205 . . . . . . . Decl: *(obj @ 196)
206 . . . . . . . Data: nil
207 . . . . . . . Type: nil
208 . . . . . . }
209 . . . . . }
210 . . . . }
211 . . . . Type: *ast.SelectorExpr {
212 . . . . . X: *ast.Ident {
213 . . . . . . NamePos: -
214 . . . . . . Name: "io"
215 . . . . . . Obj: nil
216 . . . . . }
217 . . . . . Sel: *ast.Ident {
218 . . . . . . NamePos: -
219 . . . . . . Name: "Medium"
220 . . . . . . Obj: nil
221 . . . . . }
222 . . . . }
223 . . . . Tag: nil
224 . . . . Comment: nil
225 . . . }
226 . . }
227 . . Closing: -
228 . }
229 . Incomplete: false
230 }
```go
type Service struct {
core.Runtime[*Options]
activeWorkspace *Workspace
workspaceList map[string]string // Maps Workspace ID to Public Key
medium io.Medium
}
``` ```
Service manages user workspaces. Service manages user workspaces.
#### Methods #### Methods
- `CreateWorkspace(identifier, password 0 *ast.Ident { - `CreateWorkspace(identifier, password string) (string, error)`: CreateWorkspace creates a new, obfuscated workspace on the local medium.
1 . NamePos: - - `HandleIPCEvents(c *core.Core, msg core.Message) error`: HandleIPCEvents processes IPC messages, including injecting dependencies on startup.
2 . Name: "string" - `ServiceStartup(context.Context, application.ServiceOptions) error`: ServiceStartup initializes the service, loading the workspace list.
3 . Obj: nil - `SwitchWorkspace(name string) error`: SwitchWorkspace changes the active workspace.
4 } - `WorkspaceFileGet(filename string) (string, error)`: WorkspaceFileGet retrieves a file from the active workspace.
) 0 *ast.Ident { - `WorkspaceFileSet(filename, content string) error`: WorkspaceFileSet writes a file to the active workspace.
1 . NamePos: - - `getWorkspaceDir() (string, error)`: getWorkspaceDir retrieves the WorkspaceDir from the config service.
2 . Name: "string"
3 . Obj: nil
4 }
, 0 *ast.Ident {
1 . NamePos: -
2 . Name: "error"
3 . Obj: nil
4 }
`: CreateWorkspace creates a new, obfuscated workspace on the local medium.
- `HandleIPCEvents(c 0 *ast.StarExpr {
1 . Star: -
2 . X: *ast.SelectorExpr {
3 . . X: *ast.Ident {
4 . . . NamePos: -
5 . . . Name: "core"
6 . . . Obj: nil
7 . . }
8 . . Sel: *ast.Ident {
9 . . . NamePos: -
10 . . . Name: "Core"
11 . . . Obj: nil
12 . . }
13 . }
14 }
, msg 0 *ast.SelectorExpr {
1 . X: *ast.Ident {
2 . . NamePos: -
3 . . Name: "core"
4 . . Obj: nil
5 . }
6 . Sel: *ast.Ident {
7 . . NamePos: -
8 . . Name: "Message"
9 . . Obj: nil
10 . }
11 }
) 0 *ast.Ident {
1 . NamePos: -
2 . Name: "error"
3 . Obj: nil
4 }
`: HandleIPCEvents processes IPC messages, including injecting dependencies on startup.
- `ServiceStartup( 0 *ast.SelectorExpr {
1 . X: *ast.Ident {
2 . . NamePos: -
3 . . Name: "context"
4 . . Obj: nil
5 . }
6 . Sel: *ast.Ident {
7 . . NamePos: -
8 . . Name: "Context"
9 . . Obj: nil
10 . }
11 }
, 0 *ast.SelectorExpr {
1 . X: *ast.Ident {
2 . . NamePos: -
3 . . Name: "application"
4 . . Obj: nil
5 . }
6 . Sel: *ast.Ident {
7 . . NamePos: -
8 . . Name: "ServiceOptions"
9 . . Obj: nil
10 . }
11 }
) 0 *ast.Ident {
1 . NamePos: -
2 . Name: "error"
3 . Obj: nil
4 }
`: ServiceStartup initializes the service, loading the workspace list.
- `SwitchWorkspace(name 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
) 0 *ast.Ident {
1 . NamePos: -
2 . Name: "error"
3 . Obj: nil
4 }
`: SwitchWorkspace changes the active workspace.
- `WorkspaceFileGet(filename 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
) 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
, 0 *ast.Ident {
1 . NamePos: -
2 . Name: "error"
3 . Obj: nil
4 }
`: WorkspaceFileGet retrieves a file from the active workspace.
- `WorkspaceFileSet(filename, content 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
) 0 *ast.Ident {
1 . NamePos: -
2 . Name: "error"
3 . Obj: nil
4 }
`: WorkspaceFileSet writes a file to the active workspace.
- `getWorkspaceDir() 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
, 0 *ast.Ident {
1 . NamePos: -
2 . Name: "error"
3 . Obj: nil
4 }
`: getWorkspaceDir retrieves the WorkspaceDir from the config service.
### `type Workspace` ### `type Workspace`
```go
type Workspace 0 *ast.StructType {
1 . Struct: -
2 . Fields: *ast.FieldList {
3 . . Opening: -
4 . . List: []*ast.Field (len = 2) {
5 . . . 0: *ast.Field {
6 . . . . Doc: nil
7 . . . . Names: []*ast.Ident (len = 1) {
8 . . . . . 0: *ast.Ident {
9 . . . . . . NamePos: -
10 . . . . . . Name: "Name"
11 . . . . . . Obj: *ast.Object {
12 . . . . . . . Kind: var
13 . . . . . . . Name: "Name"
14 . . . . . . . Decl: *(obj @ 5)
15 . . . . . . . Data: nil
16 . . . . . . . Type: nil
17 . . . . . . }
18 . . . . . }
19 . . . . }
20 . . . . Type: *ast.Ident {
21 . . . . . NamePos: -
22 . . . . . Name: "string"
23 . . . . . Obj: nil
24 . . . . }
25 . . . . Tag: nil
26 . . . . Comment: nil
27 . . . }
28 . . . 1: *ast.Field {
29 . . . . Doc: nil
30 . . . . Names: []*ast.Ident (len = 1) {
31 . . . . . 0: *ast.Ident {
32 . . . . . . NamePos: -
33 . . . . . . Name: "Path"
34 . . . . . . Obj: *ast.Object {
35 . . . . . . . Kind: var
36 . . . . . . . Name: "Path"
37 . . . . . . . Decl: *(obj @ 28)
38 . . . . . . . Data: nil
39 . . . . . . . Type: nil
40 . . . . . . }
41 . . . . . }
42 . . . . }
43 . . . . Type: *ast.Ident {
44 . . . . . NamePos: -
45 . . . . . Name: "string"
46 . . . . . Obj: nil
47 . . . . }
48 . . . . Tag: nil
49 . . . . Comment: nil
50 . . . }
51 . . }
52 . . Closing: -
53 . }
54 . Incomplete: false
55 }
```go
type Workspace struct {
Name string
Path string
}
``` ```
Workspace represents a user's workspace. Workspace represents a user's workspace.
### `type localMedium` ### `type localMedium`
```go ```go
type localMedium 0 *ast.StructType { type localMedium struct{}
1 . Struct: -
2 . Fields: *ast.FieldList {
3 . . Opening: -
4 . . List: nil
5 . . Closing: -
6 . }
7 . Incomplete: false
8 }
``` ```
localMedium implements the Medium interface for the local disk. localMedium implements the Medium interface for the local disk.
#### Methods #### Methods
- `EnsureDir(path 0 *ast.Ident { - `EnsureDir(path string) error`: EnsureDir creates a directory on the local disk.
1 . NamePos: - - `FileGet(path string) (string, error)`: FileGet reads a file from the local disk.
2 . Name: "string" - `FileSet(path, content string) error`: FileSet writes a file to the local disk.
3 . Obj: nil - `IsFile(path string) bool`: IsFile checks if a path exists and is a file on the local disk.
4 } - `Read(path string) (string, error)`: Read reads a file from the local disk.
) 0 *ast.Ident { - `Write(path, content string) error`: Write writes a file to the local disk.
1 . NamePos: -
2 . Name: "error"
3 . Obj: nil
4 }
`: EnsureDir creates a directory on the local disk.
- `FileGet(path 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
) 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
, 0 *ast.Ident {
1 . NamePos: -
2 . Name: "error"
3 . Obj: nil
4 }
`: FileGet reads a file from the local disk.
- `FileSet(path, content 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
) 0 *ast.Ident {
1 . NamePos: -
2 . Name: "error"
3 . Obj: nil
4 }
`: FileSet writes a file to the local disk.
- `IsFile(path 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
) 0 *ast.Ident {
1 . NamePos: -
2 . Name: "bool"
3 . Obj: nil
4 }
`: IsFile checks if a path exists and is a file on the local disk.
- `Read(path 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
) 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
, 0 *ast.Ident {
1 . NamePos: -
2 . Name: "error"
3 . Obj: nil
4 }
`: Read reads a file from the local disk.
- `Write(path, content 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
) 0 *ast.Ident {
1 . NamePos: -
2 . Name: "error"
3 . Obj: nil
4 }
`: Write writes a file to the local disk.
## Functions ## Functions
- `NewLocalMedium() 0 *ast.SelectorExpr { - `NewLocalMedium() io.Medium`: NewLocalMedium creates a new instance of the local storage medium.
1 . X: *ast.Ident { - `Register(c *core.Core) (any, error)`: Register is the constructor for dynamic dependency injection (used with core.WithService). It creates a Service instance and initializes its core.Runtime field. Dependencies are injected during ServiceStartup.
2 . . NamePos: -
3 . . Name: "io"
4 . . Obj: nil
5 . }
6 . Sel: *ast.Ident {
7 . . NamePos: -
8 . . Name: "Medium"
9 . . Obj: nil
10 . }
11 }
`: NewLocalMedium creates a new instance of the local storage medium.
- `Register(c 0 *ast.StarExpr {
1 . Star: -
2 . X: *ast.SelectorExpr {
3 . . X: *ast.Ident {
4 . . . NamePos: -
5 . . . Name: "core"
6 . . . Obj: nil
7 . . }
8 . . Sel: *ast.Ident {
9 . . . NamePos: -
10 . . . Name: "Core"
11 . . . Obj: nil
12 . . }
13 . }
14 }
) 0 *ast.Ident {
1 . NamePos: -
2 . Name: "any"
3 . Obj: nil
4 }
, 0 *ast.Ident {
1 . NamePos: -
2 . Name: "error"
3 . Obj: nil
4 }
`: Register is the constructor for dynamic dependency injection (used with core.WithService). It creates a Service instance and initializes its core.Runtime field. Dependencies are injected during ServiceStartup.

View file

@ -1,19 +1,21 @@
package core package core
import ( import (
"errors" "embed"
"io"
"testing" "testing"
"github.com/Snider/Core/pkg/core/testutil"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/wailsapp/wails/v3/pkg/application"
) )
// MockServiceInterface is an interface that MockService implements. func TestCore_New_Good(t *testing.T) {
type MockServiceInterface interface { c, err := New()
GetName() string assert.NoError(t, err)
assert.NotNil(t, c)
} }
// MockService is a simple struct to act as a mock service. // Mock service for testing
type MockService struct { type MockService struct {
Name string Name string
} }
@ -22,166 +24,172 @@ func (m *MockService) GetName() string {
return m.Name return m.Name
} }
func TestNew(t *testing.T) { func TestCore_WithService_Good(t *testing.T) {
c, err := New()
assert.NoError(t, err)
assert.NotNil(t, c)
assert.NotNil(t, c.services)
assert.False(t, c.servicesLocked)
}
func TestWithService(t *testing.T) {
// Test successful service registration
t.Run("successful service registration", func(t *testing.T) {
c, err := New()
assert.NoError(t, err)
factory := func(c *Core) (any, error) { factory := func(c *Core) (any, error) {
return &MockService{Name: "testService"}, nil return &MockService{Name: "test"}, nil
} }
c, err := New(WithService(factory))
err = WithService(factory)(c)
assert.NoError(t, err) assert.NoError(t, err)
// The service name is derived from the package path of MockService, which is "core"
svc := c.Service("core") svc := c.Service("core")
assert.NotNil(t, svc) assert.NotNil(t, svc)
mockSvc, ok := svc.(*MockService) mockSvc, ok := svc.(*MockService)
assert.True(t, ok) assert.True(t, ok)
assert.Equal(t, "testService", mockSvc.Name) assert.Equal(t, "test", mockSvc.GetName())
}) }
// Test service registration with factory error
t.Run("service registration with factory error", func(t *testing.T) {
c, err := New()
assert.NoError(t, err)
func TestCore_WithService_Bad(t *testing.T) {
factory := func(c *Core) (any, error) { factory := func(c *Core) (any, error) {
return nil, errors.New("factory error") return nil, assert.AnError
} }
_, err := New(WithService(factory))
err = WithService(factory)(c)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.Error(), "factory error") assert.ErrorIs(t, err, assert.AnError)
}) }
// Test service registration when services are locked func TestCore_WithWails_Good(t *testing.T) {
t.Run("service registration when locked", func(t *testing.T) { app := &application.App{}
c, err := New(WithWails(app))
assert.NoError(t, err)
assert.Equal(t, app, c.App)
}
//go:embed testdata
var testFS embed.FS
func TestCore_WithAssets_Good(t *testing.T) {
c, err := New(WithAssets(testFS))
assert.NoError(t, err)
assets := c.Assets()
file, err := assets.Open("testdata/test.txt")
assert.NoError(t, err)
defer file.Close()
content, err := io.ReadAll(file)
assert.NoError(t, err)
assert.Equal(t, "hello from testdata\n", string(content))
}
func TestCore_WithServiceLock_Good(t *testing.T) {
c, err := New(WithServiceLock()) c, err := New(WithServiceLock())
assert.NoError(t, err) assert.NoError(t, err)
err = c.RegisterService("test", &MockService{})
factory := func(c *Core) (any, error) {
return &MockService{Name: "lockedService"}, nil
}
err = WithService(factory)(c)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.Error(), "is not permitted by the serviceLock setting")
})
} }
func TestServiceFor(t *testing.T) { func TestCore_RegisterService_Good(t *testing.T) {
c, err := New() c, err := New()
assert.NoError(t, err) assert.NoError(t, err)
err = c.RegisterService("test", &MockService{Name: "test"})
// Register a mock service
err = c.RegisterService("mockservice", &MockService{Name: "testService"})
assert.NoError(t, err) assert.NoError(t, err)
svc := c.Service("test")
// Test successful retrieval as an interface assert.NotNil(t, svc)
t.Run("successful retrieval as interface", func(t *testing.T) { mockSvc, ok := svc.(*MockService)
svc, err := ServiceFor[MockServiceInterface](c, "mockservice") assert.True(t, ok)
assert.NoError(t, err) assert.Equal(t, "test", mockSvc.GetName())
assert.Equal(t, "testService", svc.GetName())
})
// Test service not found
t.Run("service not found", func(t *testing.T) {
_, err := ServiceFor[MockServiceInterface](c, "nonexistent")
assert.Error(t, err)
assert.Contains(t, err.Error(), "service 'nonexistent' not found")
})
// Test type mismatch
t.Run("type mismatch", func(t *testing.T) {
err := c.RegisterService("stringservice", "hello")
assert.NoError(t, err)
_, err = ServiceFor[MockServiceInterface](c, "stringservice")
assert.Error(t, err)
assert.Contains(t, err.Error(), "is of type string, but expected <nil>")
})
} }
func TestMustServiceFor(t *testing.T) { func TestCore_RegisterService_Bad(t *testing.T) {
c, err := New() c, err := New()
assert.NoError(t, err) assert.NoError(t, err)
err = c.RegisterService("test", &MockService{})
// Register a mock service
assert.NoError(t, c.RegisterService("mockservice", &MockService{Name: "testService"}))
// Test successful retrieval as an interface
assert.NotPanics(t, func() {
svc := MustServiceFor[MockServiceInterface](c, "mockservice")
assert.Equal(t, "testService", svc.GetName())
})
// Test service not found (should panic)
assert.Panics(t, func() {
MustServiceFor[MockServiceInterface](c, "nonexistent")
})
// Test type mismatch (should panic)
assert.NoError(t, c.RegisterService("stringservice", "hello"))
assert.Panics(t, func() {
MustServiceFor[MockServiceInterface](c, "stringservice")
})
}
func TestRegisterService(t *testing.T) {
c, err := New()
assert.NoError(t, err) assert.NoError(t, err)
err = c.RegisterService("test", &MockService{})
// Test successful registration
err = c.RegisterService("myservice", &MockService{})
assert.NoError(t, err)
assert.NotNil(t, c.Service("myservice"))
// Test duplicate registration
err = c.RegisterService("myservice", &MockService{})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.Error(), "already registered")
// Test empty name
err = c.RegisterService("", &MockService{}) err = c.RegisterService("", &MockService{})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.Error(), "service name cannot be empty")
// Test registration when locked
lockedCore, err := New(WithServiceLock())
assert.NoError(t, err)
err = lockedCore.RegisterService("lockedservice", &MockService{})
assert.Error(t, err)
assert.Contains(t, err.Error(), "is not permitted by the serviceLock setting")
} }
func TestCoreConfig(t *testing.T) { func TestCore_ServiceFor_Good(t *testing.T) {
c, err := New() c, err := New()
assert.NoError(t, err) assert.NoError(t, err)
err = c.RegisterService("test", &MockService{Name: "test"})
// Register a mock config service
mockCfg := &testutil.MockConfig{}
err = c.RegisterService("config", mockCfg)
assert.NoError(t, err) assert.NoError(t, err)
svc, err := ServiceFor[*MockService](c, "test")
assert.NoError(t, err)
assert.Equal(t, "test", svc.GetName())
}
// Test successful retrieval of Config service func TestCore_ServiceFor_Bad(t *testing.T) {
cfg := c.Config() c, err := New()
assert.NotNil(t, cfg) assert.NoError(t, err)
assert.Implements(t, (*Config)(nil), cfg) _, err = ServiceFor[*MockService](c, "nonexistent")
assert.Error(t, err)
err = c.RegisterService("test", "not a service")
assert.NoError(t, err)
_, err = ServiceFor[*MockService](c, "test")
assert.Error(t, err)
}
// Test panic if Config service not registered func TestCore_MustServiceFor_Good(t *testing.T) {
coreWithoutConfig, err := New() c, err := New()
assert.NoError(t, err)
err = c.RegisterService("test", &MockService{Name: "test"})
assert.NoError(t, err)
svc := MustServiceFor[*MockService](c, "test")
assert.Equal(t, "test", svc.GetName())
}
func TestCore_MustServiceFor_Ugly(t *testing.T) {
c, err := New()
assert.NoError(t, err) assert.NoError(t, err)
assert.Panics(t, func() { assert.Panics(t, func() {
coreWithoutConfig.Config() MustServiceFor[*MockService](c, "nonexistent")
})
err = c.RegisterService("test", "not a service")
assert.NoError(t, err)
assert.Panics(t, func() {
MustServiceFor[*MockService](c, "test")
}) })
} }
type MockAction struct {
handled bool
}
func (a *MockAction) Handle(c *Core, msg Message) error {
a.handled = true
return nil
}
func TestCore_ACTION_Good(t *testing.T) {
c, err := New()
assert.NoError(t, err)
action := &MockAction{}
c.RegisterAction(action.Handle)
err = c.ACTION(nil)
assert.NoError(t, err)
assert.True(t, action.handled)
}
func TestCore_RegisterActions_Good(t *testing.T) {
c, err := New()
assert.NoError(t, err)
action1 := &MockAction{}
action2 := &MockAction{}
c.RegisterActions(action1.Handle, action2.Handle)
err = c.ACTION(nil)
assert.NoError(t, err)
assert.True(t, action1.handled)
assert.True(t, action2.handled)
}
func TestCore_WithName_Good(t *testing.T) {
factory := func(c *Core) (any, error) {
return &MockService{Name: "test"}, nil
}
c, err := New(WithName("my-service", factory))
assert.NoError(t, err)
svc := c.Service("my-service")
assert.NotNil(t, svc)
mockSvc, ok := svc.(*MockService)
assert.True(t, ok)
assert.Equal(t, "test", mockSvc.GetName())
}
func TestCore_WithName_Bad(t *testing.T) {
factory := func(c *Core) (any, error) {
return nil, assert.AnError
}
_, err := New(WithName("my-service", factory))
assert.Error(t, err)
assert.ErrorIs(t, err, assert.AnError)
}

87
pkg/io/io_test.go Normal file
View file

@ -0,0 +1,87 @@
package io
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIO_Read_Good(t *testing.T) {
medium := NewMockMedium()
medium.Files["test.txt"] = "hello"
content, err := Read(medium, "test.txt")
assert.NoError(t, err)
assert.Equal(t, "hello", content)
}
func TestIO_Read_Bad(t *testing.T) {
medium := NewMockMedium()
_, err := Read(medium, "nonexistent.txt")
assert.Error(t, err)
}
func TestIO_Write_Good(t *testing.T) {
medium := NewMockMedium()
err := Write(medium, "test.txt", "hello")
assert.NoError(t, err)
writtenContent, ok := medium.Files["test.txt"]
assert.True(t, ok)
assert.Equal(t, "hello", writtenContent)
}
// TODO: The current MockMedium cannot simulate a write error.
// func TestIO_Write_Bad(t *testing.T) {
// medium := NewMockMedium()
// // How to make Write fail?
// err := Write(medium, "test.txt", "hello")
// assert.Error(t, err)
// }
func TestIO_EnsureDir_Good(t *testing.T) {
medium := NewMockMedium()
err := EnsureDir(medium, "testdir")
assert.NoError(t, err)
exists := medium.Dirs["testdir"]
assert.True(t, exists)
}
// TODO: The current MockMedium cannot simulate an EnsureDir error.
// func TestIO_EnsureDir_Bad(t *testing.T) {
// medium := NewMockMedium()
// // How to make EnsureDir fail?
// err := EnsureDir(medium, "testdir")
// assert.Error(t, err)
// }
func TestIO_IsFile_Good(t *testing.T) {
medium := NewMockMedium()
medium.Files["test.txt"] = "content"
assert.True(t, IsFile(medium, "test.txt"))
assert.False(t, IsFile(medium, "nonexistent.txt"))
}
func TestIO_Copy_Good(t *testing.T) {
source := NewMockMedium()
source.Files["source.txt"] = "hello"
dest := NewMockMedium()
err := Copy(source, "source.txt", dest, "dest.txt")
assert.NoError(t, err)
copiedContent, ok := dest.Files["dest.txt"]
assert.True(t, ok)
assert.Equal(t, "hello", copiedContent)
}
func TestIO_Copy_Bad(t *testing.T) {
source := NewMockMedium() // No source file
dest := NewMockMedium()
err := Copy(source, "source.txt", dest, "dest.txt")
assert.Error(t, err)
}

View file

@ -1,4 +1,4 @@
package runtime package runtime_test
import ( import (
"errors" "errors"
@ -8,67 +8,107 @@ import (
"github.com/Snider/Core/pkg/crypt" "github.com/Snider/Core/pkg/crypt"
"github.com/Snider/Core/pkg/display" "github.com/Snider/Core/pkg/display"
"github.com/Snider/Core/pkg/help" "github.com/Snider/Core/pkg/help"
"github.com/Snider/Core/pkg/i18n"
"github.com/Snider/Core/pkg/runtime"
"github.com/Snider/Core/pkg/workspace" "github.com/Snider/Core/pkg/workspace"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/wailsapp/wails/v3/pkg/application"
) )
// TestNew ensures that New correctly initializes a Runtime instance.
func TestNew(t *testing.T) { func TestNew(t *testing.T) {
// Pass nil for the application, as it is not required for this test. testCases := []struct {
runtime, err := New(nil) name string
assert.NoError(t, err) app *application.App
assert.NotNil(t, runtime) factories map[string]runtime.ServiceFactory
expectErr bool
// Assert that key services are initialized expectErrStr string
assert.NotNil(t, runtime.Core, "Core service should be initialized") checkRuntime func(*testing.T, *runtime.Runtime)
assert.NotNil(t, runtime.Config, "Config service should be initialized") }{
assert.NotNil(t, runtime.Display, "Display service should be initialized") {
assert.NotNil(t, runtime.Help, "Help service should be initialized") name: "Good path",
assert.NotNil(t, runtime.Crypt, "Crypt service should be initialized") app: nil,
assert.NotNil(t, runtime.I18n, "I18n service should be initialized") factories: map[string]runtime.ServiceFactory{
assert.NotNil(t, runtime.Workspace, "Workspace service should be initialized") "config": func() (any, error) { return &config.Service{}, nil },
"display": func() (any, error) { return &display.Service{}, nil },
// Verify services are properly wired through Core "help": func() (any, error) { return &help.Service{}, nil },
configFromCore := runtime.Core.Service("config") "crypt": func() (any, error) { return &crypt.Service{}, nil },
assert.NotNil(t, configFromCore, "Config should be registered in Core") "i18n": func() (any, error) { return &i18n.Service{}, nil },
assert.Equal(t, runtime.Config, configFromCore, "Config from Core should match direct reference") "workspace": func() (any, error) { return &workspace.Service{}, nil },
},
displayFromCore := runtime.Core.Service("display") expectErr: false,
assert.NotNil(t, displayFromCore, "Display should be registered in Core") checkRuntime: func(t *testing.T, rt *runtime.Runtime) {
assert.Equal(t, runtime.Display, displayFromCore, "Display from Core should match direct reference") assert.NotNil(t, rt)
assert.NotNil(t, rt.Core)
helpFromCore := runtime.Core.Service("help") assert.NotNil(t, rt.Config)
assert.NotNil(t, helpFromCore, "Help should be registered in Core") assert.NotNil(t, rt.Display)
assert.Equal(t, runtime.Help, helpFromCore, "Help from Core should match direct reference") assert.NotNil(t, rt.Help)
assert.NotNil(t, rt.Crypt)
cryptFromCore := runtime.Core.Service("crypt") assert.NotNil(t, rt.I18n)
assert.NotNil(t, cryptFromCore, "Crypt should be registered in Core") assert.NotNil(t, rt.Workspace)
assert.Equal(t, runtime.Crypt, cryptFromCore, "Crypt from Core should match direct reference") },
},
i18nFromCore := runtime.Core.Service("i18n") {
assert.NotNil(t, i18nFromCore, "I18n should be registered in Core") name: "Factory returns an error",
assert.Equal(t, runtime.I18n, i18nFromCore, "I18n from Core should match direct reference") app: nil,
factories: map[string]runtime.ServiceFactory{
workspaceFromCore := runtime.Core.Service("workspace") "config": func() (any, error) { return &config.Service{}, nil },
assert.NotNil(t, workspaceFromCore, "Workspace should be registered in Core") "display": func() (any, error) { return &display.Service{}, nil },
assert.Equal(t, runtime.Workspace, workspaceFromCore, "Workspace from Core should match direct reference") "help": func() (any, error) { return &help.Service{}, nil },
} "crypt": func() (any, error) { return nil, errors.New("crypt service failed") },
"i18n": func() (any, error) { return &i18n.Service{}, nil },
// TestNewServiceInitializationError tests the error path in New. "workspace": func() (any, error) { return &workspace.Service{}, nil },
func TestNewServiceInitializationError(t *testing.T) { },
factories := map[string]ServiceFactory{ expectErr: true,
"config": func() (any, error) { return config.New() }, expectErrStr: "failed to create service crypt: crypt service failed",
"display": func() (any, error) { return display.New() }, },
"help": func() (any, error) { return help.New() }, {
"crypt": func() (any, error) { return crypt.New() }, name: "Factory returns wrong type",
"i18n": func() (any, error) { return nil, errors.New("i18n service failed to initialize") }, // This factory will fail app: nil,
"workspace": func() (any, error) { return workspace.New() }, factories: map[string]runtime.ServiceFactory{
"config": func() (any, error) { return &config.Service{}, nil },
"display": func() (any, error) { return "not a display service", nil },
"help": func() (any, error) { return &help.Service{}, nil },
"crypt": func() (any, error) { return &crypt.Service{}, nil },
"i18n": func() (any, error) { return &i18n.Service{}, nil },
"workspace": func() (any, error) { return &workspace.Service{}, nil },
},
expectErr: true,
expectErrStr: "display service has unexpected type",
},
{
name: "With non-nil app",
app: &application.App{},
factories: map[string]runtime.ServiceFactory{
"config": func() (any, error) { return &config.Service{}, nil },
"display": func() (any, error) { return &display.Service{}, nil },
"help": func() (any, error) { return &help.Service{}, nil },
"crypt": func() (any, error) { return &crypt.Service{}, nil },
"i18n": func() (any, error) { return &i18n.Service{}, nil },
"workspace": func() (any, error) { return &workspace.Service{}, nil },
},
expectErr: false,
checkRuntime: func(t *testing.T, rt *runtime.Runtime) {
assert.NotNil(t, rt)
assert.NotNil(t, rt.Core)
assert.NotNil(t, rt.Core.App)
},
},
} }
// Pass nil for the application, as it is not required for this test. for _, tc := range testCases {
runtime, err := NewWithFactories(nil, factories) t.Run(tc.name, func(t *testing.T) {
rt, err := runtime.NewWithFactories(tc.app, tc.factories)
if tc.expectErr {
assert.Error(t, err) assert.Error(t, err)
assert.Nil(t, runtime) assert.Contains(t, err.Error(), tc.expectErrStr)
assert.Contains(t, err.Error(), "failed to create service i18n: i18n service failed to initialize") assert.Nil(t, rt)
} else {
assert.NoError(t, err)
if tc.checkRuntime != nil {
tc.checkRuntime(t, rt)
}
}
})
}
} }

View file

@ -1,196 +0,0 @@
package tdd
import (
"embed"
"io"
"testing"
"github.com/Snider/Core/pkg/core"
"github.com/stretchr/testify/assert"
"github.com/wailsapp/wails/v3/pkg/application"
)
func TestCore_New_Good(t *testing.T) {
c, err := core.New()
assert.NoError(t, err)
assert.NotNil(t, c)
}
// Mock service for testing
type MockService struct {
Name string
}
func (m *MockService) GetName() string {
return m.Name
}
func TestCore_WithService_Good(t *testing.T) {
factory := func(c *core.Core) (any, error) {
return &MockService{Name: "test"}, nil
}
c, err := core.New(core.WithService(factory))
assert.NoError(t, err)
svc := c.Service("tdd")
assert.NotNil(t, svc)
mockSvc, ok := svc.(*MockService)
assert.True(t, ok)
assert.Equal(t, "test", mockSvc.GetName())
}
func TestCore_WithService_Bad(t *testing.T) {
factory := func(c *core.Core) (any, error) {
return nil, assert.AnError
}
_, err := core.New(core.WithService(factory))
assert.Error(t, err)
assert.ErrorIs(t, err, assert.AnError)
}
func TestCore_WithWails_Good(t *testing.T) {
app := &application.App{}
c, err := core.New(core.WithWails(app))
assert.NoError(t, err)
assert.Equal(t, app, c.App)
}
//go:embed testdata
var testFS embed.FS
func TestCore_WithAssets_Good(t *testing.T) {
c, err := core.New(core.WithAssets(testFS))
assert.NoError(t, err)
assets := c.Assets()
file, err := assets.Open("testdata/test.txt")
assert.NoError(t, err)
defer file.Close()
content, err := io.ReadAll(file)
assert.NoError(t, err)
assert.Equal(t, "hello from testdata\n", string(content))
}
func TestCore_WithServiceLock_Good(t *testing.T) {
c, err := core.New(core.WithServiceLock())
assert.NoError(t, err)
err = c.RegisterService("test", &MockService{})
assert.Error(t, err)
}
func TestCore_RegisterService_Good(t *testing.T) {
c, err := core.New()
assert.NoError(t, err)
err = c.RegisterService("test", &MockService{Name: "test"})
assert.NoError(t, err)
svc := c.Service("test")
assert.NotNil(t, svc)
mockSvc, ok := svc.(*MockService)
assert.True(t, ok)
assert.Equal(t, "test", mockSvc.GetName())
}
func TestCore_RegisterService_Bad(t *testing.T) {
c, err := core.New()
assert.NoError(t, err)
err = c.RegisterService("test", &MockService{})
assert.NoError(t, err)
err = c.RegisterService("test", &MockService{})
assert.Error(t, err)
err = c.RegisterService("", &MockService{})
assert.Error(t, err)
}
func TestCore_ServiceFor_Good(t *testing.T) {
c, err := core.New()
assert.NoError(t, err)
err = c.RegisterService("test", &MockService{Name: "test"})
assert.NoError(t, err)
svc, err := core.ServiceFor[*MockService](c, "test")
assert.NoError(t, err)
assert.Equal(t, "test", svc.GetName())
}
func TestCore_ServiceFor_Bad(t *testing.T) {
c, err := core.New()
assert.NoError(t, err)
_, err = core.ServiceFor[*MockService](c, "nonexistent")
assert.Error(t, err)
err = c.RegisterService("test", "not a service")
assert.NoError(t, err)
_, err = core.ServiceFor[*MockService](c, "test")
assert.Error(t, err)
}
func TestCore_MustServiceFor_Good(t *testing.T) {
c, err := core.New()
assert.NoError(t, err)
err = c.RegisterService("test", &MockService{Name: "test"})
assert.NoError(t, err)
svc := core.MustServiceFor[*MockService](c, "test")
assert.Equal(t, "test", svc.GetName())
}
func TestCore_MustServiceFor_Ugly(t *testing.T) {
c, err := core.New()
assert.NoError(t, err)
assert.Panics(t, func() {
core.MustServiceFor[*MockService](c, "nonexistent")
})
err = c.RegisterService("test", "not a service")
assert.NoError(t, err)
assert.Panics(t, func() {
core.MustServiceFor[*MockService](c, "test")
})
}
type MockAction struct {
handled bool
}
func (a *MockAction) Handle(c *core.Core, msg core.Message) error {
a.handled = true
return nil
}
func TestCore_ACTION_Good(t *testing.T) {
c, err := core.New()
assert.NoError(t, err)
action := &MockAction{}
c.RegisterAction(action.Handle)
err = c.ACTION(nil)
assert.NoError(t, err)
assert.True(t, action.handled)
}
func TestCore_RegisterActions_Good(t *testing.T) {
c, err := core.New()
assert.NoError(t, err)
action1 := &MockAction{}
action2 := &MockAction{}
c.RegisterActions(action1.Handle, action2.Handle)
err = c.ACTION(nil)
assert.NoError(t, err)
assert.True(t, action1.handled)
assert.True(t, action2.handled)
}
func TestCore_WithName_Good(t *testing.T) {
factory := func(c *core.Core) (any, error) {
return &MockService{Name: "test"}, nil
}
c, err := core.New(core.WithName("my-service", factory))
assert.NoError(t, err)
svc := c.Service("my-service")
assert.NotNil(t, svc)
mockSvc, ok := svc.(*MockService)
assert.True(t, ok)
assert.Equal(t, "test", mockSvc.GetName())
}
func TestCore_WithName_Bad(t *testing.T) {
factory := func(c *core.Core) (any, error) {
return nil, assert.AnError
}
_, err := core.New(core.WithName("my-service", factory))
assert.Error(t, err)
assert.ErrorIs(t, err, assert.AnError)
}

View file

@ -1,160 +0,0 @@
package tdd
import (
"errors"
"testing"
"github.com/Snider/Core/pkg/io"
"github.com/stretchr/testify/assert"
)
type MockMedium struct {
ReadFileFunc func(path string) (string, error)
WriteFileFunc func(path, content string) error
EnsureDirFunc func(path string) error
IsFileFunc func(path string) bool
}
func (m *MockMedium) Read(path string) (string, error) {
if m.ReadFileFunc != nil {
return m.ReadFileFunc(path)
}
return "", errors.New("not implemented")
}
func (m *MockMedium) Write(path, content string) error {
if m.WriteFileFunc != nil {
return m.WriteFileFunc(path, content)
}
return errors.New("not implemented")
}
func (m *MockMedium) EnsureDir(path string) error {
if m.EnsureDirFunc != nil {
return m.EnsureDirFunc(path)
}
return errors.New("not implemented")
}
func (m *MockMedium) IsFile(path string) bool {
if m.IsFileFunc != nil {
return m.IsFileFunc(path)
}
return false
}
func (m *MockMedium) FileGet(path string) (string, error) {
return m.Read(path)
}
func (m *MockMedium) FileSet(path, content string) error {
return m.Write(path, content)
}
func TestIO_Read_Good(t *testing.T) {
medium := &MockMedium{
ReadFileFunc: func(path string) (string, error) {
assert.Equal(t, "test.txt", path)
return "hello", nil
},
}
content, err := io.Read(medium, "test.txt")
assert.NoError(t, err)
assert.Equal(t, "hello", content)
}
func TestIO_Read_Bad(t *testing.T) {
medium := &MockMedium{
ReadFileFunc: func(path string) (string, error) {
return "", assert.AnError
},
}
_, err := io.Read(medium, "test.txt")
assert.Error(t, err)
assert.ErrorIs(t, err, assert.AnError)
}
func TestIO_Write_Good(t *testing.T) {
medium := &MockMedium{
WriteFileFunc: func(path, content string) error {
assert.Equal(t, "test.txt", path)
assert.Equal(t, "hello", content)
return nil
},
}
err := io.Write(medium, "test.txt", "hello")
assert.NoError(t, err)
}
func TestIO_Write_Bad(t *testing.T) {
medium := &MockMedium{
WriteFileFunc: func(path, content string) error {
return assert.AnError
},
}
err := io.Write(medium, "test.txt", "hello")
assert.Error(t, err)
assert.ErrorIs(t, err, assert.AnError)
}
func TestIO_EnsureDir_Good(t *testing.T) {
medium := &MockMedium{
EnsureDirFunc: func(path string) error {
assert.Equal(t, "testdir", path)
return nil
},
}
err := io.EnsureDir(medium, "testdir")
assert.NoError(t, err)
}
func TestIO_EnsureDir_Bad(t *testing.T) {
medium := &MockMedium{
EnsureDirFunc: func(path string) error {
return assert.AnError
},
}
err := io.EnsureDir(medium, "testdir")
assert.Error(t, err)
assert.ErrorIs(t, err, assert.AnError)
}
func TestIO_IsFile_Good(t *testing.T) {
medium := &MockMedium{
IsFileFunc: func(path string) bool {
assert.Equal(t, "test.txt", path)
return true
},
}
assert.True(t, io.IsFile(medium, "test.txt"))
}
func TestIO_Copy_Good(t *testing.T) {
source := &MockMedium{
ReadFileFunc: func(path string) (string, error) {
assert.Equal(t, "source.txt", path)
return "hello", nil
},
}
dest := &MockMedium{
WriteFileFunc: func(path, content string) error {
assert.Equal(t, "dest.txt", path)
assert.Equal(t, "hello", content)
return nil
},
}
err := io.Copy(source, "source.txt", dest, "dest.txt")
assert.NoError(t, err)
}
func TestIO_Copy_Bad(t *testing.T) {
source := &MockMedium{
ReadFileFunc: func(path string) (string, error) {
return "", assert.AnError
},
}
dest := &MockMedium{}
err := io.Copy(source, "source.txt", dest, "dest.txt")
assert.Error(t, err)
assert.ErrorIs(t, err, assert.AnError)
}

View file

@ -1,114 +0,0 @@
package tdd
import (
"errors"
"testing"
"github.com/Snider/Core/pkg/config"
"github.com/Snider/Core/pkg/crypt"
"github.com/Snider/Core/pkg/display"
"github.com/Snider/Core/pkg/help"
"github.com/Snider/Core/pkg/i18n"
"github.com/Snider/Core/pkg/runtime"
"github.com/Snider/Core/pkg/workspace"
"github.com/stretchr/testify/assert"
"github.com/wailsapp/wails/v3/pkg/application"
)
func TestNew(t *testing.T) {
testCases := []struct {
name string
app *application.App
factories map[string]runtime.ServiceFactory
expectErr bool
expectErrStr string
checkRuntime func(*testing.T, *runtime.Runtime)
}{
{
name: "Good path",
app: nil,
factories: map[string]runtime.ServiceFactory{
"config": func() (any, error) { return &config.Service{}, nil },
"display": func() (any, error) { return &display.Service{}, nil },
"help": func() (any, error) { return &help.Service{}, nil },
"crypt": func() (any, error) { return &crypt.Service{}, nil },
"i18n": func() (any, error) { return &i18n.Service{}, nil },
"workspace": func() (any, error) { return &workspace.Service{}, nil },
},
expectErr: false,
checkRuntime: func(t *testing.T, rt *runtime.Runtime) {
assert.NotNil(t, rt)
assert.NotNil(t, rt.Core)
assert.NotNil(t, rt.Config)
assert.NotNil(t, rt.Display)
assert.NotNil(t, rt.Help)
assert.NotNil(t, rt.Crypt)
assert.NotNil(t, rt.I18n)
assert.NotNil(t, rt.Workspace)
},
},
{
name: "Factory returns an error",
app: nil,
factories: map[string]runtime.ServiceFactory{
"config": func() (any, error) { return &config.Service{}, nil },
"display": func() (any, error) { return &display.Service{}, nil },
"help": func() (any, error) { return &help.Service{}, nil },
"crypt": func() (any, error) { return nil, errors.New("crypt service failed") },
"i18n": func() (any, error) { return &i18n.Service{}, nil },
"workspace": func() (any, error) { return &workspace.Service{}, nil },
},
expectErr: true,
expectErrStr: "failed to create service crypt: crypt service failed",
},
{
name: "Factory returns wrong type",
app: nil,
factories: map[string]runtime.ServiceFactory{
"config": func() (any, error) { return &config.Service{}, nil },
"display": func() (any, error) { return "not a display service", nil },
"help": func() (any, error) { return &help.Service{}, nil },
"crypt": func() (any, error) { return &crypt.Service{}, nil },
"i18n": func() (any, error) { return &i18n.Service{}, nil },
"workspace": func() (any, error) { return &workspace.Service{}, nil },
},
expectErr: true,
expectErrStr: "display service has unexpected type",
},
{
name: "With non-nil app",
app: &application.App{},
factories: map[string]runtime.ServiceFactory{
"config": func() (any, error) { return &config.Service{}, nil },
"display": func() (any, error) { return &display.Service{}, nil },
"help": func() (any, error) { return &help.Service{}, nil },
"crypt": func() (any, error) { return &crypt.Service{}, nil },
"i18n": func() (any, error) { return &i18n.Service{}, nil },
"workspace": func() (any, error) { return &workspace.Service{}, nil },
},
expectErr: false,
checkRuntime: func(t *testing.T, rt *runtime.Runtime) {
assert.NotNil(t, rt)
assert.NotNil(t, rt.Core)
assert.NotNil(t, rt.Core.App)
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
rt, err := runtime.NewWithFactories(tc.app, tc.factories)
if tc.expectErr {
assert.Error(t, err)
assert.Contains(t, err.Error(), tc.expectErrStr)
assert.Nil(t, rt)
} else {
assert.NoError(t, err)
if tc.checkRuntime != nil {
tc.checkRuntime(t, rt)
}
}
})
}
}