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
5 changes: 5 additions & 0 deletions .github/workflows/pull-request-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

jobs:
ci-lint:
runs-on: ubuntu-latest-4cores-16GB

Check failure on line 17 in .github/workflows/pull-request-main.yml

View workflow job for this annotation

GitHub Actions / ci-lint-misc

[actionlint] reported by reviewdog 🐶 label "ubuntu-latest-4cores-16GB" is unknown. available labels are "windows-latest", "windows-latest-8-cores", "windows-2022", "windows-2019", "ubuntu-latest", "ubuntu-latest-4-cores", "ubuntu-latest-8-cores", "ubuntu-latest-16-cores", "ubuntu-24.04", "ubuntu-22.04", "ubuntu-20.04", "macos-latest", "macos-latest-xl", "macos-latest-xlarge", "macos-latest-large", "macos-14-xl", "macos-14-xlarge", "macos-14-large", "macos-14", "macos-14.0", "macos-13-xl", "macos-13-xlarge", "macos-13-large", "macos-13", "macos-13.0", "macos-12-xl", "macos-12-xlarge", "macos-12-large", "macos-12", "macos-12.0", "macos-11", "macos-11.0", "self-hosted", "x64", "arm", "arm64", "linux", "macos", "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file [runner-label] Raw Output: .github/workflows/pull-request-main.yml:17:14: label "ubuntu-latest-4cores-16GB" is unknown. available labels are "windows-latest", "windows-latest-8-cores", "windows-2022", "windows-2019", "ubuntu-latest", "ubuntu-latest-4-cores", "ubuntu-latest-8-cores", "ubuntu-latest-16-cores", "ubuntu-24.04", "ubuntu-22.04", "ubuntu-20.04", "macos-latest", "macos-latest-xl", "macos-latest-xlarge", "macos-latest-large", "macos-14-xl", "macos-14-xlarge", "macos-14-large", "macos-14", "macos-14.0", "macos-13-xl", "macos-13-xlarge", "macos-13-large", "macos-13", "macos-13.0", "macos-12-xl", "macos-12-xlarge", "macos-12-large", "macos-12", "macos-12.0", "macos-11", "macos-11.0", "self-hosted", "x64", "arm", "arm64", "linux", "macos", "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file [runner-label]
permissions:
id-token: write
contents: read
Expand Down Expand Up @@ -47,6 +47,11 @@
contents: read
actions: read
steps:
- name: Install Bun
run: |
curl -fsSL https://bun.sh/install | bash
echo "$HOME/.bun/bin" >> "$GITHUB_PATH"

- name: ci-test
uses: smartcontractkit/.github/actions/ci-test-go@ci-test-go/0.3.5
with:
Expand Down Expand Up @@ -105,7 +110,7 @@
artifact-name: go-test-${{ matrix.os }}

ci-test-system:
runs-on: ubuntu-latest-4cores-16GB

Check failure on line 113 in .github/workflows/pull-request-main.yml

View workflow job for this annotation

GitHub Actions / ci-lint-misc

[actionlint] reported by reviewdog 🐶 label "ubuntu-latest-4cores-16GB" is unknown. available labels are "windows-latest", "windows-latest-8-cores", "windows-2022", "windows-2019", "ubuntu-latest", "ubuntu-latest-4-cores", "ubuntu-latest-8-cores", "ubuntu-latest-16-cores", "ubuntu-24.04", "ubuntu-22.04", "ubuntu-20.04", "macos-latest", "macos-latest-xl", "macos-latest-xlarge", "macos-latest-large", "macos-14-xl", "macos-14-xlarge", "macos-14-large", "macos-14", "macos-14.0", "macos-13-xl", "macos-13-xlarge", "macos-13-large", "macos-13", "macos-13.0", "macos-12-xl", "macos-12-xlarge", "macos-12-large", "macos-12", "macos-12.0", "macos-11", "macos-11.0", "self-hosted", "x64", "arm", "arm64", "linux", "macos", "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file [runner-label] Raw Output: .github/workflows/pull-request-main.yml:113:14: label "ubuntu-latest-4cores-16GB" is unknown. available labels are "windows-latest", "windows-latest-8-cores", "windows-2022", "windows-2019", "ubuntu-latest", "ubuntu-latest-4-cores", "ubuntu-latest-8-cores", "ubuntu-latest-16-cores", "ubuntu-24.04", "ubuntu-22.04", "ubuntu-20.04", "macos-latest", "macos-latest-xl", "macos-latest-xlarge", "macos-latest-large", "macos-14-xl", "macos-14-xlarge", "macos-14-large", "macos-14", "macos-14.0", "macos-13-xl", "macos-13-xlarge", "macos-13-large", "macos-13", "macos-13.0", "macos-12-xl", "macos-12-xlarge", "macos-12-large", "macos-12", "macos-12.0", "macos-11", "macos-11.0", "self-hosted", "x64", "arm", "arm64", "linux", "macos", "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file [runner-label]
if: false # Disable system test untill we have a version of the test that works with the new CRE CLI (Smartcon)
environment: system-test
permissions:
Expand Down
74 changes: 72 additions & 2 deletions cmd/creinit/creinit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package creinit
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"testing"

Expand All @@ -17,7 +18,9 @@ func GetTemplateFileListGo() []string {
return []string{
"README.md",
"main.go",
"workflow.go",
"workflow.yaml",
"workflow_test.go",
}
}

Expand Down Expand Up @@ -75,6 +78,54 @@ func requireNoDirExists(t *testing.T, dirPath string) {
require.Falsef(t, fi.IsDir(), "directory %s should NOT exist", dirPath)
}

// 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.
func runLanguageSpecificTests(t *testing.T, workflowDir, language string) {
t.Helper()

switch language {
case "typescript":
runTypescriptTests(t, workflowDir)
case "go":
runGoTests(t, workflowDir)
default:
t.Logf("Unknown language %q, skipping tests", language)
}
}

// runTypescriptTests executes TypeScript tests using bun.
// Follows the cre init instructions: bun install --cwd <dir> then bun test in that directory.
func runTypescriptTests(t *testing.T, workflowDir string) {
t.Helper()

t.Logf("Running TypeScript tests in %s", workflowDir)
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")

// Run tests
testCmd := exec.Command("bun", "test")
testCmd.Dir = workflowDir
testOutput, err := testCmd.CombinedOutput()
require.NoError(t, err, "bun test failed in %s:\n%s", workflowDir, string(testOutput))
t.Logf("bun test passed:\n%s", string(testOutput))
}

// runGoTests executes Go tests in the workflow directory.
func runGoTests(t *testing.T, workflowDir string) {
t.Helper()

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

testCmd := exec.Command("go", "test", "./...")
testCmd.Dir = workflowDir
testOutput, err := testCmd.CombinedOutput()
require.NoError(t, err, "go test failed in %s:\n%s", workflowDir, string(testOutput))
t.Logf("go test passed:\n%s", string(testOutput))
}

func TestInitExecuteFlows(t *testing.T) {
// All inputs are provided via flags to avoid interactive prompts
cases := []struct {
Expand All @@ -86,6 +137,7 @@ func TestInitExecuteFlows(t *testing.T) {
expectProjectDirRel string
expectWorkflowName string
expectTemplateFiles []string
language string // "go" or "typescript"
}{
{
name: "Go PoR template with all flags",
Expand All @@ -96,6 +148,7 @@ func TestInitExecuteFlows(t *testing.T) {
expectProjectDirRel: "myproj",
expectWorkflowName: "myworkflow",
expectTemplateFiles: GetTemplateFileListGo(),
language: "go",
},
{
name: "Go HelloWorld template with all flags",
Expand All @@ -106,6 +159,7 @@ func TestInitExecuteFlows(t *testing.T) {
expectProjectDirRel: "alpha",
expectWorkflowName: "default-wf",
expectTemplateFiles: GetTemplateFileListGo(),
language: "go",
},
{
name: "Go HelloWorld with different project name",
Expand All @@ -116,6 +170,7 @@ func TestInitExecuteFlows(t *testing.T) {
expectProjectDirRel: "projX",
expectWorkflowName: "workflow-X",
expectTemplateFiles: GetTemplateFileListGo(),
language: "go",
},
{
name: "Go PoR with workflow flag",
Expand All @@ -126,6 +181,7 @@ func TestInitExecuteFlows(t *testing.T) {
expectProjectDirRel: "projFlag",
expectWorkflowName: "flagged-wf",
expectTemplateFiles: GetTemplateFileListGo(),
language: "go",
},
{
name: "Go HelloWorld template by ID",
Expand All @@ -136,6 +192,7 @@ func TestInitExecuteFlows(t *testing.T) {
expectProjectDirRel: "tplProj",
expectWorkflowName: "workflow-Tpl",
expectTemplateFiles: GetTemplateFileListGo(),
language: "go",
},
{
name: "Go PoR template with rpc-url",
Expand All @@ -146,6 +203,7 @@ func TestInitExecuteFlows(t *testing.T) {
expectProjectDirRel: "porWithFlag",
expectWorkflowName: "por-wf-01",
expectTemplateFiles: GetTemplateFileListGo(),
language: "go",
},
{
name: "TS HelloWorld template with rpc-url (ignored)",
Expand All @@ -156,6 +214,18 @@ func TestInitExecuteFlows(t *testing.T) {
expectProjectDirRel: "tsWithRpcFlag",
expectWorkflowName: "ts-wf-flag",
expectTemplateFiles: GetTemplateFileListTS(),
language: "typescript",
},
{
name: "TS PoR template",
projectNameFlag: "tsPorProj",
templateIDFlag: 4, // TypeScript PoR
workflowNameFlag: "ts-por-wf",
rpcURLFlag: "https://sepolia.example/rpc",
expectProjectDirRel: "tsPorProj",
expectWorkflowName: "ts-por-wf",
expectTemplateFiles: GetTemplateFileListTS(),
language: "typescript",
},
}

Expand Down Expand Up @@ -184,8 +254,8 @@ func TestInitExecuteFlows(t *testing.T) {

projectRoot := filepath.Join(tempDir, tc.expectProjectDirRel)
validateInitProjectStructure(t, projectRoot, tc.expectWorkflowName, tc.expectTemplateFiles)
// NOTE: We deliberately don't assert Go/TS scaffolding here because the
// template chosen by prompt could vary; dedicated tests below cover both paths.

runLanguageSpecificTests(t, filepath.Join(projectRoot, tc.expectWorkflowName), tc.language)
})
}
}
Expand Down
34 changes: 1 addition & 33 deletions cmd/creinit/template/workflow/blankTemplate/main.go.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,10 @@
package main

import (
"fmt"
"log/slog"

"github.com/smartcontractkit/cre-sdk-go/capabilities/scheduler/cron"
"github.com/smartcontractkit/cre-sdk-go/cre"
"github.com/smartcontractkit/cre-sdk-go/cre/wasm"
)

type ExecutionResult struct {
Result string
}

// Workflow configuration loaded from the config.json file
type Config struct{}

// Workflow implementation with a list of capability triggers
func InitWorkflow(config *Config, logger *slog.Logger, secretsProvider cre.SecretsProvider) (cre.Workflow[*Config], error) {
// Create the trigger
cronTrigger := cron.Trigger(&cron.Config{Schedule: "*/30 * * * * *"}) // Fires every 30 seconds

// Register a handler with the trigger and a callback function
return cre.Workflow[*Config]{
cre.Handler(cronTrigger, onCronTrigger),
}, nil
}

func onCronTrigger(config *Config, runtime cre.Runtime, trigger *cron.Payload) (*ExecutionResult, error) {
logger := runtime.Logger()
scheduledTime := trigger.ScheduledExecutionTime.AsTime()
logger.Info("Cron trigger fired", "scheduledTime", scheduledTime)

// Your logic here...

return &ExecutionResult{Result: fmt.Sprintf("Fired at %s", scheduledTime)}, nil
}

func main() {
wasm.NewRunner(cre.ParseJSON[Config]).Run(InitWorkflow)
}
}
37 changes: 37 additions & 0 deletions cmd/creinit/template/workflow/blankTemplate/workflow.go.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package main

import (
"fmt"
"log/slog"

"github.com/smartcontractkit/cre-sdk-go/capabilities/scheduler/cron"
"github.com/smartcontractkit/cre-sdk-go/cre"
)

type ExecutionResult struct {
Result string
}

// Workflow configuration loaded from the config.json file
type Config struct{}

// Workflow implementation with a list of capability triggers
func InitWorkflow(config *Config, logger *slog.Logger, secretsProvider cre.SecretsProvider) (cre.Workflow[*Config], error) {
// Create the trigger
cronTrigger := cron.Trigger(&cron.Config{Schedule: "*/30 * * * * *"}) // Fires every 30 seconds

// Register a handler with the trigger and a callback function
return cre.Workflow[*Config]{
cre.Handler(cronTrigger, onCronTrigger),
}, nil
}

func onCronTrigger(config *Config, runtime cre.Runtime, trigger *cron.Payload) (*ExecutionResult, error) {
logger := runtime.Logger()
scheduledTime := trigger.ScheduledExecutionTime.AsTime()
logger.Info("Cron trigger fired", "scheduledTime", scheduledTime)

// Your logic here...

return &ExecutionResult{Result: fmt.Sprintf("Fired at %s", scheduledTime)}, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package main

import (
"strings"
"testing"
"time"

"github.com/smartcontractkit/cre-sdk-go/capabilities/scheduler/cron"
"github.com/smartcontractkit/cre-sdk-go/cre/testutils"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/timestamppb"
)

var anyExecutionTime = time.Date(2025, 7, 14, 17, 41, 57, 0, time.UTC)

func TestInitWorkflow(t *testing.T) {
config := &Config{}
runtime := testutils.NewRuntime(t, testutils.Secrets{})

workflow, err := InitWorkflow(config, runtime.Logger(), nil)
require.NoError(t, err)

require.Len(t, workflow, 1)
require.Equal(t, cron.Trigger(&cron.Config{}).CapabilityID(), workflow[0].CapabilityID())
}

func TestOnCronTrigger(t *testing.T) {
config := &Config{}
runtime := testutils.NewRuntime(t, testutils.Secrets{})

payload := &cron.Payload{
ScheduledExecutionTime: timestamppb.New(anyExecutionTime),
}

result, err := onCronTrigger(config, runtime, payload)
require.NoError(t, err)
require.NotNil(t, result)
require.Contains(t, result.Result, "Fired at")
require.Contains(t, result.Result, "2025-07-14")

logs := runtime.GetLogs()
assertLogContains(t, logs, "Cron trigger fired")
}

func assertLogContains(t *testing.T, logs [][]byte, substr string) {
t.Helper()
for _, line := range logs {
if strings.Contains(string(line), substr) {
return
}
}
var logStrings []string
for _, log := range logs {
logStrings = append(logStrings, string(log))
}
t.Fatalf("Expected logs to contain substring %q, but it was not found in logs:\n%s",
substr, strings.Join(logStrings, "\n"))
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const fetchResult = (sendRequester: ConfidentialHTTPSendRequester, config: Confi
return json(response) as ResponseValues
}

const onCronTrigger = (runtime: Runtime<Config>) => {
export const onCronTrigger = (runtime: Runtime<Config>) => {
runtime.log('Confidential HTTP workflow triggered.')

const confHTTPClient = new ConfidentialHTTPClient()
Expand All @@ -75,7 +75,7 @@ const onCronTrigger = (runtime: Runtime<Config>) => {
}
}

const initWorkflow = (config: Config) => {
export const initWorkflow = (config: Config) => {
const cron = new CronCapability()

return [handler(cron.trigger({ schedule: config.schedule }), onCronTrigger)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
},
"license": "UNLICENSED",
"dependencies": {
"@chainlink/cre-sdk": "^1.0.9",
"@chainlink/cre-sdk": "^1.1.1",
"zod": "3.25.76"
},
"devDependencies": {
Expand Down
Loading
Loading