Skip to content
Open
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
122 changes: 122 additions & 0 deletions cmd/creinit/creinit_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package creinit

import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io/fs"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"

"github.com/stretchr/testify/require"

"github.com/smartcontractkit/cre-cli/cmd/generate-bindings/bindings"
"github.com/smartcontractkit/cre-cli/internal/constants"
"github.com/smartcontractkit/cre-cli/internal/testutil"
"github.com/smartcontractkit/cre-cli/internal/testutil/chainsim"
Expand Down Expand Up @@ -78,6 +83,84 @@ func requireNoDirExists(t *testing.T, dirPath string) {
require.Falsef(t, fi.IsDir(), "directory %s should NOT exist", dirPath)
}

func hashDirectoryFiles(t *testing.T, dir string) map[string]string {
t.Helper()
hashes := make(map[string]string)
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
data, err := os.ReadFile(path)
if err != nil {
return err
}
rel, _ := filepath.Rel(dir, path)
sum := sha256.Sum256(data)
hashes[rel] = hex.EncodeToString(sum[:])
return nil
})
require.NoError(t, err)
return hashes
}

func validateGeneratedBindingsStable(t *testing.T, projectRoot string) {
t.Helper()

generatedDir := filepath.Join(projectRoot, "contracts", "evm", "src", "generated")
if _, err := os.Stat(generatedDir); os.IsNotExist(err) {
return
}

abiDir := filepath.Join(projectRoot, "contracts", "evm", "src", "abi")

// Hash all files in generated/ before regeneration
beforeHashes := hashDirectoryFiles(t, generatedDir)
require.NotEmpty(t, beforeHashes, "generated directory should not be empty")

// Find all .abi files and regenerate bindings
abiFiles, err := filepath.Glob(filepath.Join(abiDir, "*.abi"))
require.NoError(t, err)
require.NotEmpty(t, abiFiles, "abi directory should contain .abi files")

for _, abiFile := range abiFiles {
contractName := strings.TrimSuffix(filepath.Base(abiFile), ".abi")

// Find the matching package subdirectory in generated/
entries, readErr := os.ReadDir(generatedDir)
require.NoError(t, readErr)

found := false
for _, entry := range entries {
if !entry.IsDir() {
continue
}
goFile := filepath.Join(generatedDir, entry.Name(), contractName+".go")
if _, statErr := os.Stat(goFile); statErr == nil {
// Regenerate into this directory
err = bindings.GenerateBindings("", abiFile, entry.Name(), contractName, goFile)
require.NoError(t, err, "failed to regenerate bindings for %s", contractName)
found = true
break
}
}
require.True(t, found, "no matching generated directory found for contract %s", contractName)
}

// Hash all files after regeneration
afterHashes := hashDirectoryFiles(t, generatedDir)

// Compare: every file should have the same hash
require.Equal(t, len(beforeHashes), len(afterHashes), "number of generated files changed")
for file, beforeHash := range beforeHashes {
afterHash, exists := afterHashes[file]
require.True(t, exists, "generated file %s disappeared after regeneration", file)
require.Equal(t, beforeHash, afterHash, "generated file %s changed after regeneration — template is stale", file)
}
}

// runLanguageSpecificTests runs the appropriate test suite based on the language field.
// For TypeScript: runs bun install and bun test in the workflow directory.
// For Go: runs go test ./... in the workflow directory.
Expand All @@ -99,12 +182,30 @@ func runLanguageSpecificTests(t *testing.T, workflowDir, language string) {
func runTypescriptTests(t *testing.T, workflowDir string) {
t.Helper()

testFile := filepath.Join(workflowDir, "main.test.ts")
if _, err := os.Stat(testFile); os.IsNotExist(err) {
t.Logf("Skipping TS tests: no main.test.ts in %s", workflowDir)
return
}

t.Logf("Running TypeScript tests in %s", workflowDir)

// Install dependencies using bun install --cwd (as instructed by cre init)
installCmd := exec.Command("bun", "install", "--cwd", workflowDir, "--ignore-scripts")
installOutput, err := installCmd.CombinedOutput()
require.NoError(t, err, "bun install failed in %s:\n%s", workflowDir, string(installOutput))
t.Logf("bun install succeeded")

// Link local @chainlink/cre-sdk if registered (for dev with unpublished SDK changes)
linkCmd := exec.Command("bun", "link", "@chainlink/cre-sdk")
linkCmd.Dir = workflowDir
linkOutput, linkErr := linkCmd.CombinedOutput()
if linkErr != nil {
t.Logf("bun link @chainlink/cre-sdk not available, using published version: %s", string(linkOutput))
} else {
t.Logf("Linked local @chainlink/cre-sdk")
}

// Run tests
testCmd := exec.Command("bun", "test")
testCmd.Dir = workflowDir
Expand All @@ -117,6 +218,26 @@ func runTypescriptTests(t *testing.T, workflowDir string) {
func runGoTests(t *testing.T, workflowDir string) {
t.Helper()

// Check if there's a go.mod or any .go test files
hasGoTests := false
entries, err := os.ReadDir(workflowDir)
if err != nil {
t.Logf("Skipping Go tests: cannot read %s", workflowDir)
return
}

for _, entry := range entries {
if filepath.Ext(entry.Name()) == "_test.go" {
hasGoTests = true
break
}
}

if !hasGoTests {
t.Logf("Skipping Go tests: no *_test.go files in %s", workflowDir)
return
}

t.Logf("Running Go tests in %s", workflowDir)

testCmd := exec.Command("go", "test", "./...")
Expand Down Expand Up @@ -256,6 +377,7 @@ func TestInitExecuteFlows(t *testing.T) {
validateInitProjectStructure(t, projectRoot, tc.expectWorkflowName, tc.expectTemplateFiles)

runLanguageSpecificTests(t, filepath.Join(projectRoot, tc.expectWorkflowName), tc.language)
validateGeneratedBindingsStable(t, projectRoot)
})
}
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"inputs":[{"internalType":"address[]","name":"addresses","type":"address[]"}],"name":"getNativeBalances","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"typeAndVersion","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}]

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]
Loading
Loading