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:
python-version: 3.x
- run: pip install mkdocs-material
- run: go run ./cmd/core dev docgen
- run: mkdocs gh-deploy --force

View file

@ -42,13 +42,3 @@ tasks:
cmds:
- task: cov
- 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
handling across the entire codebase.
## Types
### `type Error`
`Error` represents a standardized error with operational context.
```go
type Error 0 *ast.StructType {
1 . Struct: -
2 . Fields: *ast.FieldList {
3 . . Opening: -
4 . . List: []*ast.Field (len = 3) {
5 . . . 0: *ast.Field {
6 . . . . Doc: *ast.CommentGroup {
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 }
type Error struct {
// Op is the operation being performed, e.g., "config.Load".
Op string
// Msg is a human-readable message explaining the error.
Msg string
// Err is the underlying error that was wrapped.
Err error
}
```
Error represents a standardized error with operational context.
#### Methods
- `Error() 0 *ast.Ident {
1 . NamePos: -
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.
- `Error() string`: Error returns the string representation of the error.
- `Unwrap() error`: Unwrap provides compatibility for Go's errors.Is and errors.As functions.
## Functions
- `E(op, msg 0 *ast.Ident {
1 . NamePos: -
2 . Name: "string"
3 . Obj: nil
4 }
, err 0 *ast.Ident {
1 . NamePos: -
2 . Name: "error"
3 . Obj: nil
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.
- `E(op, msg string, err error) error`: 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:
```go
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`
The `io` service provides a standardized interface for interacting with different storage backends, such as the local disk, S3, or SFTP.
## Types
### `type Medium`
`Medium` defines the standard interface for a storage backend.
```go
type Medium 0 *ast.InterfaceType {
1 . Interface: -
2 . Methods: *ast.FieldList {
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 }
type Medium interface {
// Read retrieves the content of a file as a string.
Read(path string) (string, error)
// 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`
`MockMedium` implements the `Medium` interface for testing purposes.
```go
type MockMedium 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: "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 }
type MockMedium struct {
Files map[string]string
Dirs map[string]bool
}
```
MockMedium implements the Medium interface for testing purposes.
#### Methods
- `EnsureDir(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 }
`:
- `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 }
`:
- `EnsureDir(path string) error`: Mocks creating a directory.
- `FileGet(path string) (string, error)`: Mocks reading a file.
- `FileSet(path, content string) error`: Mocks writing a file.
- `IsFile(path string) bool`: Mocks checking if a path is a file.
- `Read(path string) (string, error)`: Mocks reading a file.
- `Write(path, content string) error`: Mocks writing a file.
## Functions
- `Copy(sourceMedium 0 *ast.Ident {
1 . NamePos: -
2 . Name: "Medium"
3 . Obj: nil
4 }
, 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.
- `Copy(sourceMedium Medium, sourcePath string, destMedium Medium, destPath string) error`: Copies a file from a source medium to a destination medium.
- `EnsureDir(m Medium, path string) error`: Ensures a directory exists on the given medium.
- `IsFile(m Medium, path string) bool`: Checks if a path is a file on the given medium.
- `Read(m Medium, path string) (string, error)`: Retrieves the content of a file from the given medium.
- `Write(m Medium, path, content string) error`: Saves content to a file on the given medium.
- `EnsureDir(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: "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.
Would you like to see some examples of how to use this service?

View file

@ -3,626 +3,74 @@ title: workspace
---
# Service: `workspace`
## Constants
```godefaultWorkspacelistFile
```go
defaultWorkspacelistFile
```
## Types
### `type Options`
```go
type Options 0 *ast.StructType {
1 . Struct: -
2 . Fields: *ast.FieldList {
3 . . Opening: -
4 . . List: nil
5 . . Closing: -
6 . }
7 . Incomplete: false
8 }
type Options struct {
// Options holds configuration for the workspace service.
}
```
Options holds configuration for the workspace 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.
#### Methods
- `CreateWorkspace(identifier, password 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 }
`: 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.
- `CreateWorkspace(identifier, password string) (string, error)`: CreateWorkspace creates a new, obfuscated workspace on the local medium.
- `HandleIPCEvents(c *core.Core, msg core.Message) error`: HandleIPCEvents processes IPC messages, including injecting dependencies on startup.
- `ServiceStartup(context.Context, application.ServiceOptions) error`: ServiceStartup initializes the service, loading the workspace list.
- `SwitchWorkspace(name string) error`: SwitchWorkspace changes the active workspace.
- `WorkspaceFileGet(filename string) (string, error)`: WorkspaceFileGet retrieves a file from the active workspace.
- `WorkspaceFileSet(filename, content string) error`: WorkspaceFileSet writes a file to the active workspace.
- `getWorkspaceDir() (string, error)`: getWorkspaceDir retrieves the WorkspaceDir from the config service.
### `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.
### `type localMedium`
```go
type localMedium 0 *ast.StructType {
1 . Struct: -
2 . Fields: *ast.FieldList {
3 . . Opening: -
4 . . List: nil
5 . . Closing: -
6 . }
7 . Incomplete: false
8 }
type localMedium struct{}
```
localMedium implements the Medium interface for the local disk.
#### Methods
- `EnsureDir(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 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.
- `EnsureDir(path string) error`: EnsureDir creates a directory on the local disk.
- `FileGet(path string) (string, error)`: FileGet reads a file from the local disk.
- `FileSet(path, content string) error`: FileSet writes a file to the local disk.
- `IsFile(path string) bool`: IsFile checks if a path exists and is a file on the local disk.
- `Read(path string) (string, error)`: Read reads a file from the local disk.
- `Write(path, content string) error`: Write writes a file to the local disk.
## Functions
- `NewLocalMedium() 0 *ast.SelectorExpr {
1 . X: *ast.Ident {
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.
- `NewLocalMedium() io.Medium`: NewLocalMedium creates a new instance of the local storage medium.
- `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.

View file

@ -1,19 +1,21 @@
package core
import (
"errors"
"embed"
"io"
"testing"
"github.com/Snider/Core/pkg/core/testutil"
"github.com/stretchr/testify/assert"
"github.com/wailsapp/wails/v3/pkg/application"
)
// MockServiceInterface is an interface that MockService implements.
type MockServiceInterface interface {
GetName() string
func TestCore_New_Good(t *testing.T) {
c, err := New()
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 {
Name string
}
@ -22,166 +24,172 @@ func (m *MockService) GetName() string {
return m.Name
}
func TestNew(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)
func TestCore_WithService_Good(t *testing.T) {
factory := func(c *Core) (any, error) {
return &MockService{Name: "testService"}, nil
return &MockService{Name: "test"}, nil
}
err = WithService(factory)(c)
c, err := New(WithService(factory))
assert.NoError(t, err)
// The service name is derived from the package path of MockService, which is "core"
svc := c.Service("core")
assert.NotNil(t, svc)
mockSvc, ok := svc.(*MockService)
assert.True(t, ok)
assert.Equal(t, "testService", mockSvc.Name)
})
// Test service registration with factory error
t.Run("service registration with factory error", func(t *testing.T) {
c, err := New()
assert.NoError(t, err)
factory := func(c *Core) (any, error) {
return nil, errors.New("factory error")
assert.Equal(t, "test", mockSvc.GetName())
}
err = WithService(factory)(c)
func TestCore_WithService_Bad(t *testing.T) {
factory := func(c *Core) (any, error) {
return nil, assert.AnError
}
_, err := New(WithService(factory))
assert.Error(t, err)
assert.Contains(t, err.Error(), "factory error")
})
assert.ErrorIs(t, err, assert.AnError)
}
// Test service registration when services are locked
t.Run("service registration when locked", func(t *testing.T) {
func TestCore_WithWails_Good(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())
assert.NoError(t, err)
factory := func(c *Core) (any, error) {
return &MockService{Name: "lockedService"}, nil
}
err = WithService(factory)(c)
err = c.RegisterService("test", &MockService{})
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()
assert.NoError(t, err)
// Register a mock service
err = c.RegisterService("mockservice", &MockService{Name: "testService"})
err = c.RegisterService("test", &MockService{Name: "test"})
assert.NoError(t, err)
// Test successful retrieval as an interface
t.Run("successful retrieval as interface", func(t *testing.T) {
svc, err := ServiceFor[MockServiceInterface](c, "mockservice")
assert.NoError(t, err)
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>")
})
svc := c.Service("test")
assert.NotNil(t, svc)
mockSvc, ok := svc.(*MockService)
assert.True(t, ok)
assert.Equal(t, "test", mockSvc.GetName())
}
func TestMustServiceFor(t *testing.T) {
func TestCore_RegisterService_Bad(t *testing.T) {
c, err := New()
assert.NoError(t, err)
// 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()
err = c.RegisterService("test", &MockService{})
assert.NoError(t, err)
// 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{})
err = c.RegisterService("test", &MockService{})
assert.Error(t, err)
assert.Contains(t, err.Error(), "already registered")
// Test empty name
err = c.RegisterService("", &MockService{})
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()
assert.NoError(t, err)
// Register a mock config service
mockCfg := &testutil.MockConfig{}
err = c.RegisterService("config", mockCfg)
err = c.RegisterService("test", &MockService{Name: "test"})
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
cfg := c.Config()
assert.NotNil(t, cfg)
assert.Implements(t, (*Config)(nil), cfg)
func TestCore_ServiceFor_Bad(t *testing.T) {
c, err := New()
assert.NoError(t, err)
_, 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
coreWithoutConfig, err := New()
func TestCore_MustServiceFor_Good(t *testing.T) {
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.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 (
"errors"
@ -8,67 +8,107 @@ import (
"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"
)
// TestNew ensures that New correctly initializes a Runtime instance.
func TestNew(t *testing.T) {
// Pass nil for the application, as it is not required for this test.
runtime, err := New(nil)
assert.NoError(t, err)
assert.NotNil(t, runtime)
// Assert that key services are initialized
assert.NotNil(t, runtime.Core, "Core service should be initialized")
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")
assert.NotNil(t, runtime.Crypt, "Crypt service should be initialized")
assert.NotNil(t, runtime.I18n, "I18n service should be initialized")
assert.NotNil(t, runtime.Workspace, "Workspace service should be initialized")
// Verify services are properly wired through Core
configFromCore := runtime.Core.Service("config")
assert.NotNil(t, configFromCore, "Config should be registered in Core")
assert.Equal(t, runtime.Config, configFromCore, "Config from Core should match direct reference")
displayFromCore := runtime.Core.Service("display")
assert.NotNil(t, displayFromCore, "Display should be registered in Core")
assert.Equal(t, runtime.Display, displayFromCore, "Display from Core should match direct reference")
helpFromCore := runtime.Core.Service("help")
assert.NotNil(t, helpFromCore, "Help should be registered in Core")
assert.Equal(t, runtime.Help, helpFromCore, "Help from Core should match direct reference")
cryptFromCore := runtime.Core.Service("crypt")
assert.NotNil(t, cryptFromCore, "Crypt should be registered in Core")
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")
assert.Equal(t, runtime.I18n, i18nFromCore, "I18n from Core should match direct reference")
workspaceFromCore := runtime.Core.Service("workspace")
assert.NotNil(t, workspaceFromCore, "Workspace should be registered in Core")
assert.Equal(t, runtime.Workspace, workspaceFromCore, "Workspace from Core should match direct reference")
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)
},
},
}
// TestNewServiceInitializationError tests the error path in New.
func TestNewServiceInitializationError(t *testing.T) {
factories := map[string]ServiceFactory{
"config": func() (any, error) { return config.New() },
"display": func() (any, error) { return display.New() },
"help": func() (any, error) { return help.New() },
"crypt": func() (any, error) { return crypt.New() },
"i18n": func() (any, error) { return nil, errors.New("i18n service failed to initialize") }, // This factory will fail
"workspace": func() (any, error) { return workspace.New() },
}
// Pass nil for the application, as it is not required for this test.
runtime, err := NewWithFactories(nil, factories)
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.Nil(t, runtime)
assert.Contains(t, err.Error(), "failed to create service i18n: i18n service failed to initialize")
assert.Contains(t, err.Error(), tc.expectErrStr)
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)
}
}
})
}
}