Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions cmd/hatchet-cli/cli/internal/templater/selection_test.go

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the use case of having this test? To me it seems like every time we make changes to the quickstart repo / examples we will need to keep updating this which seems like some wasted work?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mnafees thanks for the feedback. I wanted to clarify a few things and suggest a design improvement for a follow-up PR.

If the quickstart repo's implementation changes but the contract does not, the only hatchet repo update is bumping go.mod and go.sum to the new release. Have a look at the recent v0.3.1 bump in this PR (fc3768b) as an example. It changed the scheduled TypeScript template's input handling, and the only edits here in that commit are a test rename and comment cleanup.

The intent of selection_test is to validate what the CLI depends on, which use cases and languages exist and which selections validate. The CLI automatically discovers which use cases the embedded quickstarts includes. In contrast, new languages are not automatically discovered, because the CLI hardcodes the language list and each language's package managers. The assertions only catch removals. Without them if a release dropped a language from a use case, the CLI would silently stop offering it while the dashboard onboarding would continue to print commands for it.

The design should be improved. If hatchet-quickstarts published a manifest of use cases, languages, and package managers alongside the templates, the CLI could derive everything from it and any tests would move to the quickstarts repo where the changes actually happen. Neither adding nor removing a language would require CLI code change. That is a design change though, so I would like to implement it in a follow-up PR.

Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import (
"reflect"
"strings"
"testing"
"testing/fstest"

quickstarts "github.com/hatchet-dev/hatchet-quickstarts"
)

func TestUseCases(t *testing.T) {
func TestUseCasesListsDefaultFirstThenDiscovered(t *testing.T) {
useCases, err := UseCases(quickstarts.TemplatesFS())
if err != nil {
t.Fatalf("UseCases returned an error: %v", err)
Expand Down Expand Up @@ -49,8 +50,8 @@ func TestLanguagesFor(t *testing.T) {
t.Fatalf("LanguagesFor(scheduled) returned an error: %v", err)
}

if !reflect.DeepEqual(scheduled, []string{"go"}) {
t.Errorf("expected only go for scheduled, got %v", scheduled)
if !reflect.DeepEqual(scheduled, []string{"python", "typescript", "go"}) {
t.Errorf("expected all three languages for scheduled, got %v", scheduled)
}
}

Expand All @@ -60,6 +61,8 @@ func TestValidate(t *testing.T) {
valid := []Selection{
{UseCase: "simple", Language: "python", PackageManager: "poetry"},
{UseCase: "", Language: "typescript", PackageManager: "pnpm"},
{UseCase: "scheduled", Language: "python", PackageManager: "pip"},
{UseCase: "scheduled", Language: "typescript", PackageManager: "npm"},
{UseCase: "scheduled", Language: "go", PackageManager: "go"},
}

Expand All @@ -74,7 +77,6 @@ func TestValidate(t *testing.T) {
wantErr string
}{
{Selection{UseCase: "nonexistent", Language: "go", PackageManager: "go"}, "unknown use case"},
{Selection{UseCase: "scheduled", Language: "python", PackageManager: "poetry"}, "does not support language"},
{Selection{UseCase: "simple", Language: "rust", PackageManager: "cargo"}, "invalid language"},
{Selection{UseCase: "simple", Language: "go", PackageManager: "npm"}, "invalid package manager"},
}
Expand All @@ -92,6 +94,18 @@ func TestValidate(t *testing.T) {
}
}

func TestValidateUnsupportedLanguageForUseCase(t *testing.T) {
fsys := fstest.MapFS{
"templates/go/README.md": {Data: []byte("simple")},
"templates/use-cases/partial/go/README.md": {Data: []byte("go only")},
}

err := Validate(fsys, Selection{UseCase: "partial", Language: "python", PackageManager: "poetry"})
if err == nil || !strings.Contains(err.Error(), "does not support language") {
t.Errorf("expected a does-not-support error, got: %v", err)
}
}

func TestProcessMultiSourceScheduledGo(t *testing.T) {
dstDir := filepath.Join(t.TempDir(), "project")
sel := Selection{UseCase: "scheduled", Language: "go", PackageManager: "go"}
Expand Down
81 changes: 60 additions & 21 deletions cmd/hatchet-cli/cli/quickstart_e2e_test.go

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same question on this test file as well

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mnafees Mostly the same answer as above. However, this file belongs in the CLI because it tests that the released CLI generates the project, starts the worker, and completes the trigger. While content edits do not touch this file either, adding or removing a combination currently does require a one-line list update. With my proposed manifest follow-up PR its lists can be derived from the manifest, i.e. that maintenance is removed.

Original file line number Diff line number Diff line change
Expand Up @@ -26,28 +26,57 @@ type templateTestCase struct {
trigger string
}

// Test matrix of all use case, language, and package manager combinations
var templateTests = []templateTestCase{
// Python
{"simple", "python", "poetry", "simple"},
{"simple", "python", "uv", "simple"},
{"simple", "python", "pip", "simple"},

// TypeScript
{"simple", "typescript", "npm", "simple"},
{"simple", "typescript", "pnpm", "simple"},
{"simple", "typescript", "yarn", "simple"},
{"simple", "typescript", "bun", "simple"},

// Go
{"simple", "go", "go", "simple"},

// Use cases
{"scheduled", "go", "go", "manual-run"},
// This suite proves the released CLI end to end against the embedded
// quickstarts module, from an accepted selection through generation,
// worker startup, and a completed trigger. The quickstarts repository
// tests template content but cannot test this CLI integration. Content
// edits inside a template do not touch this file; adding or removing a
// supported combination is an intentional contract change made in the two
// lists below.

// languagePackageManagers is the supported matrix, shared by every use
// case the CLI currently ships.
var languagePackageManagers = []struct {
language string
packageManager string
}{
{"python", "poetry"},
{"python", "uv"},
{"python", "pip"},
{"typescript", "npm"},
{"typescript", "pnpm"},
{"typescript", "yarn"},
{"typescript", "bun"},
{"go", "go"},
}

// useCaseTriggers pairs each use case with the trigger its templates
// register.
var useCaseTriggers = []struct {
useCase string
trigger string
}{
{"simple", "simple"},
{"scheduled", "manual-run"},
}

func templateTests() []templateTestCase {
var tests []templateTestCase
for _, uc := range useCaseTriggers {
for _, lp := range languagePackageManagers {
tests = append(tests, templateTestCase{
useCase: uc.useCase,
language: lp.language,
packageManager: lp.packageManager,
trigger: uc.trigger,
})
}
}
return tests
}

func TestQuickstartTemplates(t *testing.T) {
for _, tt := range templateTests {
for _, tt := range templateTests() {
t.Run(fmt.Sprintf("%s_%s_%s", tt.useCase, tt.language, tt.packageManager), func(t *testing.T) {
testTemplate(t, tt)
})
Expand Down Expand Up @@ -232,11 +261,16 @@ func verifyProjectStructure(t *testing.T, projectDir string, tt templateTestCase
// Language-specific files
switch tt.language {
case "python":
pythonWorkflowFile := "src/workflows/first_workflow.py"
if tt.useCase == "scheduled" {
pythonWorkflowFile = "src/workflows/scheduled_workflow.py"
}

pythonFiles := []string{
"src/hatchet_client.py",
"src/run.py",
"src/worker.py",
"src/workflows/first_workflow.py",
pythonWorkflowFile,
}
for _, file := range pythonFiles {
path := filepath.Join(projectDir, file)
Expand All @@ -262,11 +296,16 @@ func verifyProjectStructure(t *testing.T, projectDir string, tt templateTestCase
}

case "typescript":
tsWorkflowFile := "src/workflows/first-workflow.ts"
if tt.useCase == "scheduled" {
tsWorkflowFile = "src/workflows/scheduled-workflow.ts"
}

tsFiles := []string{
"src/hatchet-client.ts",
"src/run.ts",
"src/worker.ts",
"src/workflows/first-workflow.ts",
tsWorkflowFile,
"tsconfig.json",
"package.json",
}
Expand Down
2 changes: 2 additions & 0 deletions frontend/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
"@types/dagre": "^0.7.54",
"@types/jsdom": "^28.0.3",
"@types/node": "^20.19.43",
"@types/qs": "^6.15.1",
"@types/react": "^18.3.20",
Expand All @@ -115,6 +116,7 @@
"eslint-plugin-unused-imports": "^3.2.0",
"postcss": "^8.5.22",
"prettier": "^3.9.6",
"jsdom": "^29.1.1",
"prettier-plugin-tailwindcss": "^0.8.1",
"tailwindcss": "^3.4.19",
"tsx": "^4.23.1",
Expand Down
Loading
Loading