Repository: nektos/act (language: go)

Issue:
conditional steps incorrectly evaluates conclusion
```
| {
|   "0": {
|     "outputs": {},
|     "conclusion": "success",
|     "outcome": "success"
|   },
|   "4": {
|     "outputs": {},
|     "conclusion": "success",
|     "outcome": "success"
|   },
|   "cache": {
|     "outputs": {
|       "cache-hit": "false"
|     },
|     "conclusion": "success",
|     "outcome": "success"
|   },
|   "install": {
|     "outputs": {},
|     "conclusion": "success",
|     "outcome": "success"
|   },
|   "setup": {
|     "outputs": {},
|     "conclusion": "success",
|     "outcome": "success"
|   }
| }
[build]   ✅  Success - Dump GitHub context
DEBU[0106] Loading slug from git directory '/Users/brian/web/.git' 
DEBU[0106] Loading revision from git directory '/Users/brian/web/.git' 
DEBU[0106] Found revision: c9ca2d85b3e7ee4ea4efa35cf435916bf616d042 
DEBU[0106] HEAD points to 'c9ca2d85b3e7ee4ea4efa35cf435916bf616d042' 
DEBU[0106] Found revision: c9ca2d85b3e7ee4ea4efa35cf435916bf616d042 
DEBU[0106] evaluating expression 'steps.install.conclusion == 'success'' 
DEBU[0106] expression 'steps.install.conclusion == 'success'' evaluated to 'false' 
DEBU[0106] Skipping step 'Vitest' due to 'steps.install.conclusion == 'success'' 
```

```
on:
  push:
    branches: [development, staging]
  pull_request:
    branches: [development, staging]

jobs:
  build:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [16.x]

    steps:
      - uses: actions/checkout@v2

      - name: Setup Node.js ${{ matrix.node-version }}
        id: setup
        uses: actions/setup-node@v2
        with:
          node-version: ${{ matrix.node-version }}

      - name: Cache dependencies
        id: cache
        uses: actions/cache@v2
        with:
          path: ~/.npm
          key: node-${{ matrix.node-version }}-npm-${{ hashFiles('package-lock.json') }}
          restore-keys: npm-

      - name: Install dependencies
        id: install
        if: steps.cache.outputs.cache-hit != 'true'
        run: npm ci --ignore-scripts

      - name: Dump GitHub context
        env:
          GITHUB_CONTEXT: ${{ toJson(steps) }}
        run: echo "$GITHUB_CONTEXT"

      - name: Vitest
        id: test
        if: steps.install.outcome == 'success'
        run: npm test
        continue-on-error: true

      - name: Typescript compiler
        id: typescript
        if: steps.install.outcome == 'success'
        run: npm run tsc
        continue-on-error: true
```

Expected behavior is that `steps.install.outcome == 'success'` evaluates to true, matching the result from the context log.



Discussion / hints:
This bug has confused me in my PR https://github.com/nektos/act/pull/1078#issuecomment-1079459355 while writing tests. The problem is that `outcome` is stored internally as an integer and the new expression evaluator of act v0.2.26 didn't handle this case.

https://github.com/nektos/act/blob/a85e89d3fb8e1a3b2e9dda843e18aa819095e56e/pkg/model/step_result.go#L19-L32

I think the expression evaluator should either test for the `TextMarshaler` interface and return a string instead of an integer or refactor the code that such special things no longer needs to be handled.

Fix patch:
diff --git a/pkg/exprparser/interpreter.go b/pkg/exprparser/interpreter.go
index 6287e701bb7..a9f8fafbdf0 100644
--- a/pkg/exprparser/interpreter.go
+++ b/pkg/exprparser/interpreter.go
@@ -1,6 +1,7 @@
 package exprparser
 
 import (
+	"encoding"
 	"fmt"
 	"math"
 	"reflect"
@@ -224,7 +225,16 @@ func (impl *interperterImpl) getPropertyValue(left reflect.Value, property strin
 			return "", nil
 		}
 
-		return fieldValue.Interface(), nil
+		i := fieldValue.Interface()
+		// The type stepStatus int is an integer, but should be treated as string
+		if m, ok := i.(encoding.TextMarshaler); ok {
+			text, err := m.MarshalText()
+			if err != nil {
+				return nil, err
+			}
+			return string(text), nil
+		}
+		return i, nil
 
 	case reflect.Map:
 		iter := left.MapRange()


Test patch:
diff --git a/pkg/exprparser/interpreter_test.go b/pkg/exprparser/interpreter_test.go
index c6b15c30b39..4eae253261b 100644
--- a/pkg/exprparser/interpreter_test.go
+++ b/pkg/exprparser/interpreter_test.go
@@ -534,6 +534,22 @@ func TestContexts(t *testing.T) {
 		{"env.TEST", "value", "env-context"},
 		{"job.status", "success", "job-context"},
 		{"steps.step-id.outputs.name", "value", "steps-context"},
+		{"steps.step-id.conclusion", "success", "steps-context-conclusion"},
+		{"steps.step-id.conclusion && true", true, "steps-context-conclusion"},
+		{"steps.step-id2.conclusion", "skipped", "steps-context-conclusion"},
+		{"steps.step-id2.conclusion && true", true, "steps-context-conclusion"},
+		{"steps.step-id.outcome", "success", "steps-context-outcome"},
+		{"steps.step-id['outcome']", "success", "steps-context-outcome"},
+		{"steps.step-id.outcome == 'success'", true, "steps-context-outcome"},
+		{"steps.step-id['outcome'] == 'success'", true, "steps-context-outcome"},
+		{"steps.step-id.outcome && true", true, "steps-context-outcome"},
+		{"steps['step-id']['outcome'] && true", true, "steps-context-outcome"},
+		{"steps.step-id2.outcome", "failure", "steps-context-outcome"},
+		{"steps.step-id2.outcome && true", true, "steps-context-outcome"},
+		// Disabled, since the interpreter is still too broken
+		// {"contains(steps.*.outcome, 'success')", true, "steps-context-array-outcome"},
+		// {"contains(steps.*.outcome, 'failure')", true, "steps-context-array-outcome"},
+		// {"contains(steps.*.outputs.name, 'value')", true, "steps-context-array-outputs"},
 		{"runner.os", "Linux", "runner-context"},
 		{"secrets.name", "value", "secrets-context"},
 		{"strategy.fail-fast", true, "strategy-context"},
@@ -558,6 +574,10 @@ func TestContexts(t *testing.T) {
 					"name": "value",
 				},
 			},
+			"step-id2": {
+				Outcome:    model.StepStatusFailure,
+				Conclusion: model.StepStatusSkipped,
+			},
 		},
 		Runner: map[string]interface{}{
 			"os":         "Linux",
diff --git a/pkg/runner/expression_test.go b/pkg/runner/expression_test.go
index 9cbf3b694a0..944070c335d 100644
--- a/pkg/runner/expression_test.go
+++ b/pkg/runner/expression_test.go
@@ -160,14 +160,14 @@ func TestEvaluateStep(t *testing.T) {
 		out     interface{}
 		errMesg string
 	}{
-		{"steps.idwithnothing.conclusion", model.StepStatusSuccess, ""},
-		{"steps.idwithnothing.outcome", model.StepStatusFailure, ""},
+		{"steps.idwithnothing.conclusion", model.StepStatusSuccess.String(), ""},
+		{"steps.idwithnothing.outcome", model.StepStatusFailure.String(), ""},
 		{"steps.idwithnothing.outputs.foowithnothing", "barwithnothing", ""},
-		{"steps.id-with-hyphens.conclusion", model.StepStatusSuccess, ""},
-		{"steps.id-with-hyphens.outcome", model.StepStatusFailure, ""},
+		{"steps.id-with-hyphens.conclusion", model.StepStatusSuccess.String(), ""},
+		{"steps.id-with-hyphens.outcome", model.StepStatusFailure.String(), ""},
 		{"steps.id-with-hyphens.outputs.foo-with-hyphens", "bar-with-hyphens", ""},
-		{"steps.id_with_underscores.conclusion", model.StepStatusSuccess, ""},
-		{"steps.id_with_underscores.outcome", model.StepStatusFailure, ""},
+		{"steps.id_with_underscores.conclusion", model.StepStatusSuccess.String(), ""},
+		{"steps.id_with_underscores.outcome", model.StepStatusFailure.String(), ""},
 		{"steps.id_with_underscores.outputs.foo_with_underscores", "bar_with_underscores", ""},
 	}