chore: import upstream snapshot with attribution
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
package new
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"flag"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// TestZeroToOneContract locks the documented getting-started path:
|
||||
// `micro new helloworld` must produce an ordinary Go service that the Go
|
||||
// toolchain can build, run long enough to start, and call through its generated
|
||||
// handler. The generated module is pointed back at this checkout so the
|
||||
// contract stays local and deterministic in CI.
|
||||
//
|
||||
// It shells out to `micro new` (which runs `go mod tidy`) and `go build`, so
|
||||
// it needs the Go toolchain and module access; it is skipped under `-short`.
|
||||
func TestZeroToOneContract(t *testing.T) {
|
||||
generated := generateService(t, "helloworld")
|
||||
|
||||
for _, rel := range []string{"go.mod", "main.go", "handler/helloworld.go", "README.md", "Makefile"} {
|
||||
if _, err := os.Stat(filepath.Join(generated.dir, rel)); err != nil {
|
||||
t.Fatalf("generated file %s: %v", rel, err)
|
||||
}
|
||||
}
|
||||
|
||||
generated.assertLocalModule(t)
|
||||
generated.build(t)
|
||||
generated.run(t)
|
||||
generated.call(t, "Alice", "Hello Alice")
|
||||
}
|
||||
|
||||
// TestZeroToOneNoMCPContract keeps the MCP opt-out path honest. Some services
|
||||
// intentionally run without the local MCP listener, but that variant must still
|
||||
// satisfy the same 0→1 contract: scaffold, tidy, and build without additional
|
||||
// toolchain dependencies.
|
||||
func TestZeroToOneNoMCPContract(t *testing.T) {
|
||||
generated := generateService(t, "worker", "--no-mcp")
|
||||
|
||||
main, err := os.ReadFile(filepath.Join(generated.dir, "main.go"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Contains(string(main), "gateway/mcp") || strings.Contains(string(main), "WithMCP") {
|
||||
t.Fatalf("--no-mcp generated main.go with MCP wiring:\n%s", main)
|
||||
}
|
||||
|
||||
generated.assertLocalModule(t)
|
||||
generated.build(t)
|
||||
generated.run(t)
|
||||
generated.call(t, "Bob", "Hello Bob")
|
||||
}
|
||||
|
||||
func TestPrintNextStepsSurfacesFirstAgentPath(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
printNextSteps(&out, "helloworld", false)
|
||||
|
||||
for _, want := range []string{
|
||||
"cd helloworld",
|
||||
"micro agent preflight",
|
||||
"go run .",
|
||||
"micro chat",
|
||||
"micro inspect agent <name>",
|
||||
"micro agent demo",
|
||||
"micro docs",
|
||||
"your-first-agent.html",
|
||||
"zero-to-hero.html",
|
||||
"http://localhost:3001/mcp/tools",
|
||||
} {
|
||||
if !strings.Contains(out.String(), want) {
|
||||
t.Fatalf("next steps missing %q:\n%s", want, out.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrintNextStepsNoMCPSkipsMCPHints(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
printNextSteps(&out, "worker", true)
|
||||
|
||||
for _, want := range []string{"micro agent preflight", "micro chat", "micro inspect agent <name>", "micro agent demo", "micro docs"} {
|
||||
if !strings.Contains(out.String(), want) {
|
||||
t.Fatalf("--no-mcp next steps missing %q:\n%s", want, out.String())
|
||||
}
|
||||
}
|
||||
for _, notWant := range []string{"http://localhost:3001/mcp/tools", "micro mcp serve"} {
|
||||
if strings.Contains(out.String(), notWant) {
|
||||
t.Fatalf("--no-mcp next steps should not include %q:\n%s", notWant, out.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type generatedService struct {
|
||||
dir string
|
||||
repoRoot string
|
||||
}
|
||||
|
||||
func generateService(t *testing.T, name string, args ...string) generatedService {
|
||||
t.Helper()
|
||||
|
||||
if testing.Short() {
|
||||
t.Skip("contract test shells out to the Go toolchain; skipped with -short")
|
||||
}
|
||||
|
||||
repoRoot, err := filepath.Abs(filepath.Join("..", "..", "..", ".."))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("MICRO_NEW_GO_MICRO_REPLACE", repoRoot)
|
||||
|
||||
tmp := t.TempDir()
|
||||
oldwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Chdir(tmp); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Chdir(oldwd)
|
||||
|
||||
set := flag.NewFlagSet("micro-new", flag.ContinueOnError)
|
||||
set.Bool("no-mcp", false, "")
|
||||
set.Bool("proto", false, "")
|
||||
set.String("template", "", "")
|
||||
set.String("prompt", "", "")
|
||||
set.String("provider", "", "")
|
||||
set.String("api_key", "", "")
|
||||
if err := set.Parse(append(args, name)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ctx := cli.NewContext(cli.NewApp(), set, nil)
|
||||
|
||||
if err := Run(ctx); err != nil {
|
||||
t.Fatalf("micro new %s %s: %v", strings.Join(args, " "), name, err)
|
||||
}
|
||||
|
||||
return generatedService{dir: filepath.Join(tmp, name), repoRoot: repoRoot}
|
||||
}
|
||||
|
||||
func (g generatedService) assertLocalModule(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
modPath := filepath.Join(g.dir, "go.mod")
|
||||
mod, err := os.ReadFile(modPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := "replace go-micro.dev/v6 => " + filepath.ToSlash(g.repoRoot)
|
||||
if !strings.Contains(string(mod), want) {
|
||||
t.Fatalf("generated go.mod missing local replace %q:\n%s", want, mod)
|
||||
}
|
||||
}
|
||||
|
||||
func (g generatedService) build(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
cmd := exec.Command("go", "build", "./...")
|
||||
cmd.Dir = g.dir
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("generated service go build ./... failed: %v\n%s", err, out)
|
||||
}
|
||||
}
|
||||
|
||||
func (g generatedService) run(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
bin := filepath.Join(g.dir, "service-contract")
|
||||
build := exec.Command("go", "build", "-o", bin, ".")
|
||||
build.Dir = g.dir
|
||||
if out, err := build.CombinedOutput(); err != nil {
|
||||
t.Fatalf("generated service go build -o service-contract . failed: %v\n%s", err, out)
|
||||
}
|
||||
|
||||
cmd := exec.Command(bin)
|
||||
cmd.Dir = g.dir
|
||||
var out strings.Builder
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &out
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Fatalf("generated service failed to start: %v\n%s", err, out.String())
|
||||
}
|
||||
done := make(chan error, 1)
|
||||
go func() { done <- cmd.Wait() }()
|
||||
|
||||
select {
|
||||
case err := <-done:
|
||||
t.Fatalf("generated service exited early: %v\n%s", err, out.String())
|
||||
case <-time.After(2 * time.Second):
|
||||
}
|
||||
|
||||
if err := cmd.Process.Kill(); err != nil && !errors.Is(err, os.ErrProcessDone) {
|
||||
t.Fatalf("failed to stop generated service: %v\n%s", err, out.String())
|
||||
}
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatalf("generated service did not stop after kill\n%s", out.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (g generatedService) call(t *testing.T, name, want string) {
|
||||
t.Helper()
|
||||
|
||||
testPath := filepath.Join(g.dir, "handler", "contract_test.go")
|
||||
testSrc := `package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGeneratedCallContract(t *testing.T) {
|
||||
rsp := new(Response)
|
||||
if err := New().Call(context.Background(), &Request{Name: "` + name + `"}, rsp); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if rsp.Msg != "` + want + `" {
|
||||
t.Fatalf("Call response = %q, want %q", rsp.Msg, "` + want + `")
|
||||
}
|
||||
}
|
||||
`
|
||||
if err := os.WriteFile(testPath, []byte(testSrc), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cmd := exec.Command("go", "test", "./handler", "-run", "TestGeneratedCallContract", "-count=1")
|
||||
cmd.Dir = g.dir
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("generated service call contract failed: %v\n%s", err, out)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user