Skip to content

Commit 6e9cbce

Browse files
authored
Allow for IE to resolve remote markdown documents into scenarios (#190)
This adds the ability for the Innovation Engine to fetch markdown files from both local and remote locations and resolve them into scenarios that can be executed by our engine.
1 parent 03da889 commit 6e9cbce

3 files changed

Lines changed: 114 additions & 10 deletions

File tree

internal/engine/common.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ func compareCommandOutputs(
3838
actualOutput,
3939
)
4040
results, err := lib.CompareJsonStrings(actualOutput, expectedOutput, expectedSimilarity)
41-
4241
if err != nil {
4342
return err
4443
}

internal/engine/scenario.go

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package engine
22

33
import (
44
"fmt"
5+
"io"
6+
"net/http"
57
"os"
68
"path/filepath"
79
"regexp"
@@ -32,7 +34,7 @@ type Scenario struct {
3234
// This organizes the codeblocks into steps that can be executed linearly.
3335
func groupCodeBlocksIntoSteps(blocks []parsers.CodeBlock) []Step {
3436
var groupedSteps []Step
35-
var headerIndex = make(map[string]int)
37+
headerIndex := make(map[string]int)
3638

3739
for _, block := range blocks {
3840
if index, ok := headerIndex[block.Header]; ok {
@@ -49,6 +51,37 @@ func groupCodeBlocksIntoSteps(blocks []parsers.CodeBlock) []Step {
4951
return groupedSteps
5052
}
5153

54+
// Download the scenario markdown over http
55+
func downloadScenarioMarkdown(url string) ([]byte, error) {
56+
resp, err := http.Get(url)
57+
if err != nil {
58+
return nil, err
59+
}
60+
61+
defer resp.Body.Close()
62+
63+
body, err := io.ReadAll(resp.Body)
64+
if err != nil {
65+
return nil, err
66+
}
67+
68+
return body, nil
69+
}
70+
71+
// Given either a local or remote path to a markdown file, resolve the path to
72+
// the markdown file and return the contents of the file.
73+
func resolveMarkdownSource(path string) ([]byte, error) {
74+
if strings.HasPrefix(path, "https://") || strings.HasPrefix(path, "http://") {
75+
return downloadScenarioMarkdown(path)
76+
}
77+
78+
if !fs.FileExists(path) {
79+
return nil, fmt.Errorf("markdown file '%s' does not exist", path)
80+
}
81+
82+
return os.ReadFile(path)
83+
}
84+
5285
// Creates a scenario object from a given markdown file. languagesToExecute is
5386
// used to filter out code blocks that should not be parsed out of the markdown
5487
// file.
@@ -57,13 +90,9 @@ func CreateScenarioFromMarkdown(
5790
languagesToExecute []string,
5891
environmentVariableOverrides map[string]string,
5992
) (*Scenario, error) {
60-
if !fs.FileExists(path) {
61-
return nil, fmt.Errorf("markdown file '%s' does not exist", path)
62-
}
63-
64-
source, err := os.ReadFile(path)
93+
source, err := resolveMarkdownSource(path)
6594
if err != nil {
66-
panic(err)
95+
return nil, err
6796
}
6897

6998
// Load environment variables
@@ -76,7 +105,6 @@ func CreateScenarioFromMarkdown(
76105
} else {
77106
logging.GlobalLogger.Infof("INI file '%s' exists, loading...", markdownINI)
78107
environmentVariables, err = parsers.ParseINIFile(markdownINI)
79-
80108
if err != nil {
81109
return nil, err
82110
}
@@ -170,7 +198,6 @@ func CreateScenarioFromMarkdown(
170198
}, nil
171199
}
172200

173-
174201
// Convert a scenario into a shell script
175202
func (s *Scenario) ToShellScript() string {
176203
var script strings.Builder

internal/engine/scenario_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package engine
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"net/http/httptest"
7+
"os"
8+
"strings"
9+
"testing"
10+
)
11+
12+
// Mock HTTP server for testing downloading markdown from URL
13+
func mockHTTPServer(content string) *httptest.Server {
14+
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
15+
fmt.Fprint(w, content)
16+
}))
17+
}
18+
19+
func TestResolveMarkdownSource(t *testing.T) {
20+
// Test downloading from URL
21+
t.Run("Download markdown from URL", func(t *testing.T) {
22+
content := "Test content from URL"
23+
mockServer := mockHTTPServer(content)
24+
defer mockServer.Close()
25+
26+
url := mockServer.URL
27+
result, err := resolveMarkdownSource(url)
28+
if err != nil {
29+
t.Errorf("Unexpected error: %v", err)
30+
}
31+
32+
expected := []byte(content)
33+
if string(result) != string(expected) {
34+
t.Errorf("Expected content to be %q, got %q", expected, result)
35+
}
36+
})
37+
38+
// Test reading from local file
39+
t.Run("Read from a local file", func(t *testing.T) {
40+
content := "Test content from local file"
41+
temporaryFile, err := os.CreateTemp("", "example")
42+
if err != nil {
43+
t.Fatalf("Error creating temporary file: %v", err)
44+
}
45+
defer os.Remove(temporaryFile.Name())
46+
47+
if _, err := temporaryFile.Write([]byte(content)); err != nil {
48+
t.Fatalf("Error writing to temporary file: %v", err)
49+
}
50+
if err := temporaryFile.Close(); err != nil {
51+
t.Fatalf("Error closing temporary file: %v", err)
52+
}
53+
54+
path := temporaryFile.Name()
55+
result, err := resolveMarkdownSource(path)
56+
if err != nil {
57+
t.Errorf("Unexpected error: %v", err)
58+
}
59+
60+
expected := []byte(content)
61+
if string(result) != string(expected) {
62+
t.Errorf("Expected content to be %q, got %q", expected, result)
63+
}
64+
})
65+
66+
// Test non-existing file
67+
t.Run("Non-existing file", func(t *testing.T) {
68+
nonExistingPath := "non_existing_file.md"
69+
_, err := resolveMarkdownSource(nonExistingPath)
70+
if err == nil {
71+
t.Error("Expected error for non-existing file, but got nil")
72+
}
73+
expectedErrorMsg := fmt.Sprintf("markdown file '%s' does not exist", nonExistingPath)
74+
if !strings.Contains(err.Error(), expectedErrorMsg) {
75+
t.Errorf("Expected error message to contain %q, got %q", expectedErrorMsg, err.Error())
76+
}
77+
})
78+
}

0 commit comments

Comments
 (0)