Skip to content

Commit 071ed13

Browse files
bmcdonald3davidallendj
authored andcommitted
Add test for example 1 complete workflow (#23)
* Add import Signed-off-by: Ben McDonald <ben.mcdonald@hpe.com> * Add helpers Signed-off-by: Ben McDonald <ben.mcdonald@hpe.com> * Add example 1 test Signed-off-by: Ben McDonald <ben.mcdonald@hpe.com> * Remove outdated step Signed-off-by: Ben McDonald <ben.mcdonald@hpe.com> Signed-off-by: David Allen <davidallendj@gmail.com>
1 parent 9966815 commit 071ed13

File tree

3 files changed

+188
-9
lines changed

3 files changed

+188
-9
lines changed

examples/01-basic-crud/README.md

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -454,17 +454,12 @@ fabrica generate
454454
# 6. Update dependencies
455455
go mod tidy
456456

457-
# 7. Uncomment in cmd/server/main.go:
458-
# - import "github.com/user/device-inventory/internal/storage"
459-
# - storage.InitFileBackend("./data")
460-
# - RegisterGeneratedRoutes(r)
461-
462-
# 8. Build server and client
457+
# 7. Build server and client
463458
go build -o server ./cmd/server
464459
fabrica generate --client
465460
go build -o client ./cmd/client
466461

467-
# 9. Run and test
462+
# 8. Run and test
468463
./server # In one terminal
469464
./client device list # In another terminal
470465

test/integration/clean_test.go

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,99 @@ func (s *FabricaTestSuite) TestCreateFRUApplication() {
224224

225225
// For file storage, check storage file instead of Ent schema
226226
project.AssertFileExists("internal/storage/storage_generated.go")
227-
} // Run the test suite
227+
}
228+
229+
func (s *FabricaTestSuite) TestExample1_EndToEnd() {
230+
// Create project
231+
project := s.createProject("example1-crud", "github.com/test/example1", "file")
232+
233+
// 1. Initialize project
234+
err := project.Initialize(s.fabricaBinary)
235+
s.Require().NoError(err, "project initialization should succeed")
236+
237+
// 2. Add resource
238+
err = project.AddResource(s.fabricaBinary, "Device")
239+
s.Require().NoError(err, "adding resource should succeed")
240+
241+
// 3. Customize resource (New Step)
242+
err = project.Example1_CustomizeResource()
243+
s.Require().NoError(err, "customizing resource should succeed")
244+
245+
// 4. Generate code
246+
err = project.Generate(s.fabricaBinary)
247+
s.Require().NoError(err, "code generation should succeed")
248+
249+
// 5. Configure server (New Step)
250+
err = project.Example1_ConfigureServer()
251+
s.Require().NoError(err, "configuring server main.go should succeed")
252+
253+
// 6. Build project
254+
err = project.Build()
255+
s.Require().NoError(err, "project should build successfully")
256+
257+
// 7. Start server
258+
err = project.StartServer()
259+
s.Require().NoError(err, "server should start successfully")
260+
// Ensure server is stopped when test finishes
261+
s.T().Cleanup(func() {
262+
project.StopServer()
263+
})
264+
265+
// 8. Run Client Tests (Full CRUD)
266+
267+
// CREATE
268+
createSpec := map[string]interface{}{
269+
"description": "Core network switch",
270+
"ipAddress": "192.168.1.10",
271+
"location": "DataCenter A",
272+
"rack": "R42",
273+
}
274+
created, err := project.CreateResource("device", createSpec)
275+
s.Require().NoError(err, "client create should succeed")
276+
s.Require().NotNil(created, "created resource should not be nil")
277+
278+
// Verify metadata and spec
279+
uid, ok := created["metadata"].(map[string]interface{})["uid"].(string)
280+
s.Require().True(ok, "should get uid from metadata")
281+
s.Require().NotEmpty(uid, "uid should not be empty")
282+
project.AssertResourceHasSpec(s.T(), created, createSpec)
283+
284+
// LIST
285+
listed, err := project.ListResources("device")
286+
s.Require().NoError(err, "client list should succeed")
287+
s.Require().Len(listed, 1, "list should return one device")
288+
s.Require().Equal(uid, listed[0]["metadata"].(map[string]interface{})["uid"].(string))
289+
290+
// GET
291+
got, err := project.GetResource("device", uid)
292+
s.Require().NoError(err, "client get should succeed")
293+
project.AssertResourceHasSpec(s.T(), got, createSpec)
294+
295+
// PATCH (Example 1 doesn't test this, but we can use the helper)
296+
patchSpec := map[string]interface{}{
297+
"location": "DataCenter B",
298+
}
299+
patched, err := project.PatchResource("device", uid, patchSpec)
300+
s.Require().NoError(err, "client patch should succeed")
301+
302+
// Verify patch
303+
s.Require().Equal("DataCenter B", patched["spec"].(map[string]interface{})["location"], "location should be updated")
304+
s.Require().Equal("192.168.1.10", patched["spec"].(map[string]interface{})["ipAddress"], "ipAddress should be unchanged")
305+
306+
// DELETE
307+
err = project.DeleteResource("device", uid)
308+
s.Require().NoError(err, "client delete should succeed")
309+
310+
// VERIFY DELETE
311+
listedAfterDelete, err := project.ListResources("device")
312+
s.Require().NoError(err, "client list after delete should succeed")
313+
s.Require().Len(listedAfterDelete, 0, "list should be empty after delete")
314+
315+
_, err = project.GetResource("device", uid)
316+
s.Require().Error(err, "client get after delete should fail")
317+
}
318+
319+
// Run the test suite
228320
func TestFabricaTestSuite(t *testing.T) {
229321
suite.Run(t, new(FabricaTestSuite))
230322
}

test/integration/helpers.go

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"os/exec"
1313
"path/filepath"
1414
"time"
15+
"strings"
1516

1617
"github.com/stretchr/testify/require"
1718
"github.com/stretchr/testify/suite"
@@ -317,7 +318,7 @@ func (p *TestProject) PatchResource(resourceName, id string, patch interface{})
317318
patchJSON = string(patchBytes)
318319
}
319320

320-
output, err := p.RunClient(resourceName, "patch", id, "--patch", patchJSON)
321+
output, err := p.RunClient(resourceName, "patch", id, "--spec", patchJSON)
321322
if err != nil {
322323
return nil, fmt.Errorf("patch failed: %w\nOutput: %s", err, output)
323324
}
@@ -356,3 +357,94 @@ func (p *TestProject) AssertResourceHasSpec(t require.TestingT, resource map[str
356357
require.Equal(t, expectedValue, actualValue, "spec[%s] should match expected value", key)
357358
}
358359
}
360+
361+
// ModifyFile reads a file, applies a modification function, and writes it back
362+
func (p *TestProject) ModifyFile(relativePath string, modifier func(string) string) error {
363+
path := filepath.Join(p.Dir, relativePath)
364+
content, err := os.ReadFile(path)
365+
if err != nil {
366+
return fmt.Errorf("failed to read file %s: %w", path, err)
367+
}
368+
369+
newContent := modifier(string(content))
370+
371+
if err := os.WriteFile(path, []byte(newContent), 0644); err != nil {
372+
return fmt.Errorf("failed to write file %s: %w", path, err)
373+
}
374+
return nil
375+
}
376+
377+
// Example1_CustomizeResource updates the Device spec as per Example 1
378+
func (p *TestProject) Example1_CustomizeResource() error {
379+
// Path: pkg/resources/device/device.go
380+
relPath := filepath.Join("pkg", "resources", "device", "device.go")
381+
382+
return p.ModifyFile(relPath, func(content string) string {
383+
// We replace the default placeholder or the simple struct definition
384+
// with the full definition from the example
385+
target := `type DeviceSpec struct {
386+
Description string ` + "`json:\"description,omitempty\" validate:\"max=200\"`" + `
387+
// Add your spec fields here
388+
}`
389+
390+
replacement := `type DeviceSpec struct {
391+
Description string ` + "`json:\"description,omitempty\" validate:\"max=200\"`" + `
392+
IPAddress string ` + "`json:\"ipAddress,omitempty\" validate:\"omitempty,ip\"`" + `
393+
Location string ` + "`json:\"location,omitempty\"`" + `
394+
Rack string ` + "`json:\"rack,omitempty\"`" + `
395+
}`
396+
// Try specific replacement first
397+
if strings.Contains(content, target) {
398+
return strings.Replace(content, target, replacement, 1)
399+
}
400+
401+
// Fallback: If formatting is slightly different, try to inject just the fields
402+
// This assumes the file contains "// Add your spec fields here"
403+
fields := `IPAddress string ` + "`json:\"ipAddress,omitempty\" validate:\"omitempty,ip\"`" + `
404+
Location string ` + "`json:\"location,omitempty\"`" + `
405+
Rack string ` + "`json:\"rack,omitempty\"`"
406+
407+
return strings.Replace(content, "// Add your spec fields here", fields, 1)
408+
})
409+
}
410+
411+
// Example1_ConfigureServer uncomments the storage and route registration in main.go
412+
func (p *TestProject) Example1_ConfigureServer() error {
413+
relPath := filepath.Join("cmd", "server", "main.go")
414+
415+
return p.ModifyFile(relPath, func(content string) string {
416+
// 1. Uncomment the storage import
417+
// Expecting: // "github.com/user/device-inventory/internal/storage"
418+
// We need to be careful to match the actual module name or just the suffix
419+
lines := strings.Split(content, "\n")
420+
var newLines []string
421+
422+
for _, line := range lines {
423+
trimmed := strings.TrimSpace(line)
424+
425+
// Uncomment import for storage
426+
if strings.HasPrefix(trimmed, "//") && strings.Contains(trimmed, "/internal/storage\"") {
427+
line = strings.Replace(line, "// ", "", 1)
428+
line = strings.Replace(line, "//", "", 1) // Handle case without space
429+
}
430+
431+
// Uncomment storage init
432+
// Expecting: // storage.InitFileBackend("./data")
433+
if strings.HasPrefix(trimmed, "//") && strings.Contains(trimmed, "storage.InitFileBackend") {
434+
line = strings.Replace(line, "// ", "", 1)
435+
line = strings.Replace(line, "//", "", 1)
436+
}
437+
438+
// Uncomment route registration
439+
// Expecting: // RegisterGeneratedRoutes(r)
440+
if strings.HasPrefix(trimmed, "//") && strings.Contains(trimmed, "RegisterGeneratedRoutes") {
441+
line = strings.Replace(line, "// ", "", 1)
442+
line = strings.Replace(line, "//", "", 1)
443+
}
444+
445+
newLines = append(newLines, line)
446+
}
447+
448+
return strings.Join(newLines, "\n")
449+
})
450+
}

0 commit comments

Comments
 (0)