github.com/Jeffail/benthos/v3@v3.65.0/lib/stream/manager/api_test.go (about)

     1  package manager_test
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"os"
    11  	"path/filepath"
    12  	"reflect"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/Jeffail/benthos/v3/lib/cache"
    17  	"github.com/Jeffail/benthos/v3/lib/input"
    18  	"github.com/Jeffail/benthos/v3/lib/log"
    19  	bmanager "github.com/Jeffail/benthos/v3/lib/manager"
    20  	"github.com/Jeffail/benthos/v3/lib/message"
    21  	"github.com/Jeffail/benthos/v3/lib/metrics"
    22  	"github.com/Jeffail/benthos/v3/lib/output"
    23  	"github.com/Jeffail/benthos/v3/lib/stream"
    24  	"github.com/Jeffail/benthos/v3/lib/stream/manager"
    25  	"github.com/Jeffail/benthos/v3/lib/types"
    26  	"github.com/Jeffail/gabs/v2"
    27  	"github.com/gorilla/mux"
    28  	"github.com/stretchr/testify/assert"
    29  	"github.com/stretchr/testify/require"
    30  	yaml "gopkg.in/yaml.v3"
    31  
    32  	_ "github.com/Jeffail/benthos/v3/public/components/all"
    33  )
    34  
    35  func router(m *manager.Type) *mux.Router {
    36  	router := mux.NewRouter()
    37  	router.HandleFunc("/streams", m.HandleStreamsCRUD)
    38  	router.HandleFunc("/streams/{id}", m.HandleStreamCRUD)
    39  	router.HandleFunc("/streams/{id}/stats", m.HandleStreamStats)
    40  	router.HandleFunc("/resources/{type}/{id}", m.HandleResourceCRUD)
    41  	return router
    42  }
    43  
    44  func genRequest(verb, url string, payload interface{}) *http.Request {
    45  	var body io.Reader
    46  
    47  	if payload != nil {
    48  		bodyBytes, err := json.Marshal(payload)
    49  		if err != nil {
    50  			panic(err)
    51  		}
    52  		body = bytes.NewReader(bodyBytes)
    53  	}
    54  
    55  	req, err := http.NewRequest(verb, url, body)
    56  	if err != nil {
    57  		panic(err)
    58  	}
    59  
    60  	return req
    61  }
    62  
    63  func genYAMLRequest(verb, url string, payload interface{}) *http.Request {
    64  	var body io.Reader
    65  
    66  	if payload != nil {
    67  		bodyBytes, err := yaml.Marshal(payload)
    68  		if err != nil {
    69  			panic(err)
    70  		}
    71  		body = bytes.NewReader(bodyBytes)
    72  	}
    73  
    74  	req, err := http.NewRequest(verb, url, body)
    75  	if err != nil {
    76  		panic(err)
    77  	}
    78  
    79  	return req
    80  }
    81  
    82  type listItemBody struct {
    83  	Active    bool    `json:"active"`
    84  	Uptime    float64 `json:"uptime"`
    85  	UptimeStr string  `json:"uptime_str"`
    86  }
    87  
    88  type listBody map[string]listItemBody
    89  
    90  func parseListBody(data *bytes.Buffer) listBody {
    91  	result := listBody{}
    92  	if err := json.Unmarshal(data.Bytes(), &result); err != nil {
    93  		panic(err)
    94  	}
    95  	return result
    96  }
    97  
    98  type getBody struct {
    99  	Active    bool          `json:"active"`
   100  	Uptime    float64       `json:"uptime"`
   101  	UptimeStr string        `json:"uptime_str"`
   102  	Config    stream.Config `json:"config"`
   103  }
   104  
   105  func parseGetBody(t *testing.T, data *bytes.Buffer) getBody {
   106  	t.Helper()
   107  	result := getBody{
   108  		Config: stream.NewConfig(),
   109  	}
   110  	if err := yaml.Unmarshal(data.Bytes(), &result); err != nil {
   111  		t.Fatal(err)
   112  	}
   113  	return result
   114  }
   115  
   116  type endpointReg struct {
   117  	endpoints map[string]http.HandlerFunc
   118  	types.DudMgr
   119  }
   120  
   121  func (f *endpointReg) RegisterEndpoint(path, desc string, h http.HandlerFunc) {
   122  	f.endpoints[path] = h
   123  }
   124  
   125  func TestTypeAPIDisabled(t *testing.T) {
   126  	r := &endpointReg{endpoints: map[string]http.HandlerFunc{}}
   127  	_ = manager.New(
   128  		manager.OptSetLogger(log.Noop()),
   129  		manager.OptSetStats(metrics.Noop()),
   130  		manager.OptSetManager(r),
   131  		manager.OptSetAPITimeout(time.Millisecond*100),
   132  		manager.OptAPIEnabled(true),
   133  	)
   134  	assert.NotEmpty(t, r.endpoints)
   135  
   136  	r = &endpointReg{endpoints: map[string]http.HandlerFunc{}}
   137  	_ = manager.New(
   138  		manager.OptSetLogger(log.Noop()),
   139  		manager.OptSetStats(metrics.Noop()),
   140  		manager.OptSetManager(r),
   141  		manager.OptSetAPITimeout(time.Millisecond*100),
   142  		manager.OptAPIEnabled(false),
   143  	)
   144  	assert.Len(t, r.endpoints, 1)
   145  	assert.Contains(t, r.endpoints, "/ready")
   146  }
   147  
   148  func TestTypeAPIBadMethods(t *testing.T) {
   149  	mgr := manager.New(
   150  		manager.OptSetLogger(log.Noop()),
   151  		manager.OptSetStats(metrics.Noop()),
   152  		manager.OptSetManager(types.DudMgr{}),
   153  		manager.OptSetAPITimeout(time.Millisecond*100),
   154  	)
   155  
   156  	r := router(mgr)
   157  
   158  	request := genRequest("DELETE", "/streams", nil)
   159  	response := httptest.NewRecorder()
   160  	r.ServeHTTP(response, request)
   161  	if exp, act := http.StatusBadRequest, response.Code; exp != act {
   162  		t.Errorf("Unexpected result: %v != %v", act, exp)
   163  	}
   164  
   165  	request = genRequest("DERP", "/streams/foo", nil)
   166  	response = httptest.NewRecorder()
   167  	r.ServeHTTP(response, request)
   168  	if exp, act := http.StatusBadRequest, response.Code; exp != act {
   169  		t.Errorf("Unexpected result: %v != %v", act, exp)
   170  	}
   171  }
   172  
   173  func harmlessConf() stream.Config {
   174  	c := stream.NewConfig()
   175  	c.Input.Type = "http_server"
   176  	c.Output.Type = "http_server"
   177  	return c
   178  }
   179  
   180  func TestTypeAPIBasicOperations(t *testing.T) {
   181  	mgr := manager.New(
   182  		manager.OptSetLogger(log.Noop()),
   183  		manager.OptSetStats(metrics.Noop()),
   184  		manager.OptSetManager(types.NoopMgr()),
   185  		manager.OptSetAPITimeout(time.Second*10),
   186  	)
   187  
   188  	r := router(mgr)
   189  	conf, err := harmlessConf().Sanitised()
   190  	require.NoError(t, err)
   191  
   192  	request := genRequest("PUT", "/streams/foo", conf)
   193  	response := httptest.NewRecorder()
   194  	r.ServeHTTP(response, request)
   195  	require.Equal(t, http.StatusNotFound, response.Code, response.Body.String())
   196  
   197  	request = genRequest("GET", "/streams/foo", nil)
   198  	response = httptest.NewRecorder()
   199  	r.ServeHTTP(response, request)
   200  	assert.Equal(t, http.StatusNotFound, response.Code)
   201  
   202  	request = genRequest("POST", "/streams/foo", conf)
   203  	response = httptest.NewRecorder()
   204  	r.ServeHTTP(response, request)
   205  	assert.Equal(t, http.StatusOK, response.Code)
   206  
   207  	request = genRequest("POST", "/streams/foo", conf)
   208  	response = httptest.NewRecorder()
   209  	r.ServeHTTP(response, request)
   210  	assert.Equal(t, http.StatusBadRequest, response.Code)
   211  
   212  	request = genRequest("GET", "/streams/bar", nil)
   213  	response = httptest.NewRecorder()
   214  	r.ServeHTTP(response, request)
   215  	assert.Equal(t, http.StatusNotFound, response.Code)
   216  
   217  	request = genRequest("GET", "/streams/foo", conf)
   218  	response = httptest.NewRecorder()
   219  	r.ServeHTTP(response, request)
   220  	assert.Equal(t, http.StatusOK, response.Code)
   221  
   222  	info := parseGetBody(t, response.Body)
   223  	assert.True(t, info.Active)
   224  
   225  	actSanit, err := info.Config.Sanitised()
   226  	require.NoError(t, err)
   227  	assert.Equal(t, conf, actSanit)
   228  
   229  	newConf := harmlessConf()
   230  	newConf.Buffer.Type = "memory"
   231  	newConfSanit, err := newConf.Sanitised()
   232  	require.NoError(t, err)
   233  
   234  	request = genRequest("PUT", "/streams/foo", newConfSanit)
   235  	response = httptest.NewRecorder()
   236  	r.ServeHTTP(response, request)
   237  	assert.Equal(t, http.StatusOK, response.Code, response.Body.String())
   238  
   239  	request = genRequest("GET", "/streams/foo", conf)
   240  	response = httptest.NewRecorder()
   241  	r.ServeHTTP(response, request)
   242  	assert.Equal(t, http.StatusOK, response.Code, response.Body.String())
   243  
   244  	info = parseGetBody(t, response.Body)
   245  	assert.True(t, info.Active)
   246  
   247  	actSanit, err = info.Config.Sanitised()
   248  	require.NoError(t, err)
   249  	assert.Equal(t, newConfSanit, actSanit)
   250  
   251  	request = genRequest("DELETE", "/streams/foo", conf)
   252  	response = httptest.NewRecorder()
   253  	r.ServeHTTP(response, request)
   254  	assert.Equal(t, http.StatusOK, response.Code, response.Body.String())
   255  
   256  	request = genRequest("DELETE", "/streams/foo", conf)
   257  	response = httptest.NewRecorder()
   258  	r.ServeHTTP(response, request)
   259  	assert.Equal(t, http.StatusNotFound, response.Code, response.Body.String())
   260  
   261  	testVar := "__TEST_INPUT_TYPE"
   262  	originalEnv, orignalSet := os.LookupEnv(testVar)
   263  	defer func() {
   264  		_ = os.Unsetenv(testVar)
   265  		if orignalSet {
   266  			_ = os.Setenv(testVar, originalEnv)
   267  		}
   268  	}()
   269  	_ = os.Setenv(testVar, "http_server")
   270  	newConf = harmlessConf()
   271  	newConf.Input.Type = "${__TEST_INPUT_TYPE}"
   272  
   273  	request = genRequest("POST", "/streams/fooEnv?chilled=true", newConf)
   274  	response = httptest.NewRecorder()
   275  	r.ServeHTTP(response, request)
   276  	assert.Equal(t, http.StatusOK, response.Code, response.Body.String())
   277  
   278  	request = genRequest("GET", "/streams/fooEnv", nil)
   279  	response = httptest.NewRecorder()
   280  	r.ServeHTTP(response, request)
   281  	assert.Equal(t, http.StatusOK, response.Code, response.Body.String())
   282  
   283  	info = parseGetBody(t, response.Body)
   284  	// replace the env var with the expected value in the struct
   285  	// because we will be comparing it to the rendered version.
   286  	newConf.Input.Type = "http_server"
   287  	assert.True(t, info.Active)
   288  	assert.Equal(t, newConf, info.Config)
   289  
   290  	request = genRequest("DELETE", "/streams/fooEnv", conf)
   291  	response = httptest.NewRecorder()
   292  	r.ServeHTTP(response, request)
   293  	assert.Equal(t, http.StatusOK, response.Code, response.Body.String())
   294  }
   295  
   296  func TestTypeAPIPatch(t *testing.T) {
   297  	mgr := manager.New(
   298  		manager.OptSetLogger(log.Noop()),
   299  		manager.OptSetStats(metrics.Noop()),
   300  		manager.OptSetManager(types.DudMgr{}),
   301  		manager.OptSetAPITimeout(time.Millisecond*100),
   302  	)
   303  
   304  	r := router(mgr)
   305  	conf := harmlessConf()
   306  
   307  	request := genRequest("PATCH", "/streams/foo", conf)
   308  	response := httptest.NewRecorder()
   309  	r.ServeHTTP(response, request)
   310  	if exp, act := http.StatusNotFound, response.Code; exp != act {
   311  		t.Errorf("Unexpected result: %v != %v", act, exp)
   312  	}
   313  
   314  	request = genRequest("POST", "/streams/foo?chilled=true", conf)
   315  	response = httptest.NewRecorder()
   316  	r.ServeHTTP(response, request)
   317  	if exp, act := http.StatusOK, response.Code; exp != act {
   318  		t.Errorf("Unexpected result: %v != %v", act, exp)
   319  	}
   320  
   321  	patchConf := map[string]interface{}{
   322  		"input": map[string]interface{}{
   323  			"http_server": map[string]interface{}{
   324  				"path": "/foobarbaz",
   325  			},
   326  		},
   327  	}
   328  	request = genRequest("PATCH", "/streams/foo", patchConf)
   329  	response = httptest.NewRecorder()
   330  	r.ServeHTTP(response, request)
   331  	if exp, act := http.StatusOK, response.Code; exp != act {
   332  		t.Errorf("Unexpected result: %v != %v", act, exp)
   333  	}
   334  
   335  	conf.Input.HTTPServer.Path = "/foobarbaz"
   336  	request = genRequest("GET", "/streams/foo", conf)
   337  	response = httptest.NewRecorder()
   338  	r.ServeHTTP(response, request)
   339  	if exp, act := http.StatusOK, response.Code; exp != act {
   340  		t.Errorf("Unexpected result: %v != %v", act, exp)
   341  	}
   342  	info := parseGetBody(t, response.Body)
   343  	if !info.Active {
   344  		t.Fatal("Stream not active")
   345  	}
   346  	if act, exp := info.Config.Input.HTTPServer.Path, conf.Input.HTTPServer.Path; exp != act {
   347  		t.Errorf("Unexpected config: %v != %v", act, exp)
   348  	}
   349  	if act, exp := info.Config.Input.Type, conf.Input.Type; exp != act {
   350  		t.Errorf("Unexpected config: %v != %v", act, exp)
   351  	}
   352  }
   353  
   354  func TestTypeAPIBasicOperationsYAML(t *testing.T) {
   355  	mgr := manager.New(
   356  		manager.OptSetLogger(log.Noop()),
   357  		manager.OptSetStats(metrics.Noop()),
   358  		manager.OptSetManager(types.NoopMgr()),
   359  		manager.OptSetAPITimeout(time.Second*10),
   360  	)
   361  
   362  	r := router(mgr)
   363  	conf := harmlessConf()
   364  
   365  	request := genYAMLRequest("PUT", "/streams/foo?chilled=true", conf)
   366  	response := httptest.NewRecorder()
   367  	r.ServeHTTP(response, request)
   368  	assert.Equal(t, http.StatusNotFound, response.Code)
   369  
   370  	request = genYAMLRequest("GET", "/streams/foo", nil)
   371  	response = httptest.NewRecorder()
   372  	r.ServeHTTP(response, request)
   373  	assert.Equal(t, http.StatusNotFound, response.Code)
   374  
   375  	request = genYAMLRequest("POST", "/streams/foo?chilled=true", conf)
   376  	response = httptest.NewRecorder()
   377  	r.ServeHTTP(response, request)
   378  	assert.Equal(t, http.StatusOK, response.Code)
   379  
   380  	request = genYAMLRequest("POST", "/streams/foo", conf)
   381  	response = httptest.NewRecorder()
   382  	r.ServeHTTP(response, request)
   383  	assert.Equal(t, http.StatusBadRequest, response.Code)
   384  
   385  	request = genYAMLRequest("GET", "/streams/bar", nil)
   386  	response = httptest.NewRecorder()
   387  	r.ServeHTTP(response, request)
   388  	assert.Equal(t, http.StatusNotFound, response.Code)
   389  
   390  	request = genYAMLRequest("GET", "/streams/foo", conf)
   391  	response = httptest.NewRecorder()
   392  	r.ServeHTTP(response, request)
   393  	assert.Equal(t, http.StatusOK, response.Code)
   394  
   395  	info := parseGetBody(t, response.Body)
   396  	require.True(t, info.Active)
   397  	assert.Equal(t, conf, info.Config)
   398  
   399  	newConf := harmlessConf()
   400  	newConf.Buffer.Type = "memory"
   401  
   402  	request = genYAMLRequest("PUT", "/streams/foo?chilled=true", newConf)
   403  	response = httptest.NewRecorder()
   404  	r.ServeHTTP(response, request)
   405  	assert.Equal(t, http.StatusOK, response.Code)
   406  
   407  	request = genYAMLRequest("GET", "/streams/foo", conf)
   408  	response = httptest.NewRecorder()
   409  	r.ServeHTTP(response, request)
   410  	assert.Equal(t, http.StatusOK, response.Code)
   411  
   412  	info = parseGetBody(t, response.Body)
   413  	require.True(t, info.Active)
   414  	assert.Equal(t, newConf, info.Config)
   415  
   416  	request = genYAMLRequest("DELETE", "/streams/foo", conf)
   417  	response = httptest.NewRecorder()
   418  	r.ServeHTTP(response, request)
   419  	assert.Equal(t, http.StatusOK, response.Code)
   420  
   421  	request = genYAMLRequest("DELETE", "/streams/foo", conf)
   422  	response = httptest.NewRecorder()
   423  	r.ServeHTTP(response, request)
   424  	assert.Equal(t, http.StatusNotFound, response.Code)
   425  }
   426  
   427  func TestTypeAPIList(t *testing.T) {
   428  	mgr := manager.New(
   429  		manager.OptSetLogger(log.Noop()),
   430  		manager.OptSetStats(metrics.Noop()),
   431  		manager.OptSetManager(types.DudMgr{}),
   432  		manager.OptSetAPITimeout(time.Millisecond*100),
   433  	)
   434  
   435  	r := router(mgr)
   436  
   437  	request := genRequest("GET", "/streams", nil)
   438  	response := httptest.NewRecorder()
   439  	r.ServeHTTP(response, request)
   440  	if exp, act := http.StatusOK, response.Code; exp != act {
   441  		t.Errorf("Unexpected result: %v != %v", act, exp)
   442  	}
   443  	info := parseListBody(response.Body)
   444  	if exp, act := (listBody{}), info; !reflect.DeepEqual(exp, act) {
   445  		t.Errorf("Wrong list response: %v != %v", act, exp)
   446  	}
   447  
   448  	if err := mgr.Create("foo", harmlessConf()); err != nil {
   449  		t.Fatal(err)
   450  	}
   451  
   452  	request = genRequest("GET", "/streams", nil)
   453  	response = httptest.NewRecorder()
   454  	r.ServeHTTP(response, request)
   455  	if exp, act := http.StatusOK, response.Code; exp != act {
   456  		t.Errorf("Unexpected result: %v != %v", act, exp)
   457  	}
   458  	info = parseListBody(response.Body)
   459  	if exp, act := true, info["foo"].Active; !reflect.DeepEqual(exp, act) {
   460  		t.Errorf("Wrong list response: %v != %v", act, exp)
   461  	}
   462  }
   463  
   464  func TestTypeAPISetStreams(t *testing.T) {
   465  	mgr := manager.New(
   466  		manager.OptSetLogger(log.Noop()),
   467  		manager.OptSetStats(metrics.Noop()),
   468  		manager.OptSetManager(types.DudMgr{}),
   469  		manager.OptSetAPITimeout(time.Millisecond*100),
   470  	)
   471  
   472  	r := router(mgr)
   473  
   474  	require.NoError(t, mgr.Create("foo", harmlessConf()))
   475  	require.NoError(t, mgr.Create("bar", harmlessConf()))
   476  
   477  	request := genRequest("GET", "/streams", nil)
   478  	response := httptest.NewRecorder()
   479  	r.ServeHTTP(response, request)
   480  	assert.Equal(t, http.StatusOK, response.Code)
   481  
   482  	info := parseListBody(response.Body)
   483  	assert.True(t, info["foo"].Active)
   484  	assert.True(t, info["bar"].Active)
   485  
   486  	barConf := harmlessConf()
   487  	barConf.Input.HTTPServer.Path = "BAR_ONE"
   488  	bar2Conf := harmlessConf()
   489  	bar2Conf.Input.HTTPServer.Path = "BAR_TWO"
   490  	bazConf := harmlessConf()
   491  	bazConf.Input.HTTPServer.Path = "BAZ_ONE"
   492  
   493  	streamsBody := map[string]interface{}{}
   494  	streamsBody["bar"], _ = barConf.Sanitised()
   495  	streamsBody["bar2"], _ = bar2Conf.Sanitised()
   496  	streamsBody["baz"], _ = bazConf.Sanitised()
   497  
   498  	request = genRequest("POST", "/streams", streamsBody)
   499  	response = httptest.NewRecorder()
   500  	r.ServeHTTP(response, request)
   501  	assert.Equal(t, http.StatusOK, response.Code, response.Body.String())
   502  
   503  	request = genRequest("GET", "/streams", nil)
   504  	response = httptest.NewRecorder()
   505  	r.ServeHTTP(response, request)
   506  	assert.Equal(t, http.StatusOK, response.Code, response.Body.String())
   507  
   508  	info = parseListBody(response.Body)
   509  	assert.NotContains(t, info, "foo")
   510  	assert.Contains(t, info, "bar")
   511  	assert.Contains(t, info, "baz")
   512  
   513  	request = genRequest("GET", "/streams/bar", nil)
   514  	response = httptest.NewRecorder()
   515  	r.ServeHTTP(response, request)
   516  	assert.Equal(t, http.StatusOK, response.Code, response.Body.String())
   517  
   518  	conf := parseGetBody(t, response.Body)
   519  	assert.Equal(t, "BAR_ONE", conf.Config.Input.HTTPServer.Path)
   520  
   521  	request = genRequest("GET", "/streams/bar2", nil)
   522  	response = httptest.NewRecorder()
   523  	r.ServeHTTP(response, request)
   524  	assert.Equal(t, http.StatusOK, response.Code, response.Body.String())
   525  
   526  	conf = parseGetBody(t, response.Body)
   527  	assert.Equal(t, "BAR_TWO", conf.Config.Input.HTTPServer.Path)
   528  
   529  	request = genRequest("GET", "/streams/baz", nil)
   530  	response = httptest.NewRecorder()
   531  	r.ServeHTTP(response, request)
   532  	assert.Equal(t, http.StatusOK, response.Code, response.Body.String())
   533  
   534  	conf = parseGetBody(t, response.Body)
   535  	assert.Equal(t, "BAZ_ONE", conf.Config.Input.HTTPServer.Path)
   536  }
   537  
   538  func TestTypeAPIStreamsDefaultConf(t *testing.T) {
   539  	mgr := manager.New(
   540  		manager.OptSetLogger(log.Noop()),
   541  		manager.OptSetStats(metrics.Noop()),
   542  		manager.OptSetManager(types.DudMgr{}),
   543  		manager.OptSetAPITimeout(time.Millisecond*100),
   544  	)
   545  
   546  	r := router(mgr)
   547  
   548  	body := []byte(`{
   549  	"foo": {
   550  		"input": {
   551  			"nanomsg": {}
   552  		},
   553  		"output": {
   554  			"nanomsg": {}
   555  		}
   556  	}
   557  }`)
   558  
   559  	request, err := http.NewRequest("POST", "/streams", bytes.NewReader(body))
   560  	require.NoError(t, err)
   561  
   562  	response := httptest.NewRecorder()
   563  	r.ServeHTTP(response, request)
   564  	assert.Equal(t, http.StatusOK, response.Code)
   565  
   566  	status, err := mgr.Read("foo")
   567  	require.NoError(t, err)
   568  
   569  	assert.Equal(t, status.Config().Input.Nanomsg.PollTimeout, "5s")
   570  }
   571  
   572  func TestTypeAPIStreamsLinting(t *testing.T) {
   573  	mgr := manager.New(
   574  		manager.OptSetLogger(log.Noop()),
   575  		manager.OptSetStats(metrics.Noop()),
   576  		manager.OptSetManager(types.DudMgr{}),
   577  		manager.OptSetAPITimeout(time.Millisecond*100),
   578  	)
   579  
   580  	r := router(mgr)
   581  
   582  	body := []byte(`{
   583  	"foo": {
   584  		"input": {
   585  			"nanomsg": {}
   586  		},
   587  		"output": {
   588  			"type":"nanomsg",
   589  			"file": {}
   590  		}
   591  	},
   592  	"bar": {
   593  		"input": {
   594  			"type":"nanomsg",
   595  			"file": {}
   596  		},
   597  		"output": {
   598  			"nanomsg": {}
   599  		}
   600  	}
   601  }`)
   602  
   603  	request, err := http.NewRequest("POST", "/streams", bytes.NewReader(body))
   604  	require.NoError(t, err)
   605  
   606  	response := httptest.NewRecorder()
   607  	r.ServeHTTP(response, request)
   608  	assert.Equal(t, http.StatusBadRequest, response.Code)
   609  
   610  	expLints := `{"lint_errors":["stream 'bar': line 14: field file is invalid when the component type is nanomsg (input)","stream 'foo': line 8: field file is invalid when the component type is nanomsg (output)"]}`
   611  	assert.Equal(t, expLints, response.Body.String())
   612  
   613  	request, err = http.NewRequest("POST", "/streams?chilled=true", bytes.NewReader(body))
   614  	require.NoError(t, err)
   615  
   616  	response = httptest.NewRecorder()
   617  	r.ServeHTTP(response, request)
   618  	assert.Equal(t, http.StatusOK, response.Code)
   619  }
   620  
   621  func TestTypeAPIDefaultConf(t *testing.T) {
   622  	mgr := manager.New(
   623  		manager.OptSetLogger(log.Noop()),
   624  		manager.OptSetStats(metrics.Noop()),
   625  		manager.OptSetManager(types.DudMgr{}),
   626  		manager.OptSetAPITimeout(time.Millisecond*100),
   627  	)
   628  
   629  	r := router(mgr)
   630  
   631  	body := []byte(`{
   632  	"input": {
   633  		"nanomsg": {}
   634  	},
   635  	"output": {
   636  		"nanomsg": {}
   637  	}
   638  }`)
   639  
   640  	request, err := http.NewRequest("POST", "/streams/foo", bytes.NewReader(body))
   641  	require.NoError(t, err)
   642  
   643  	response := httptest.NewRecorder()
   644  	r.ServeHTTP(response, request)
   645  	assert.Equal(t, http.StatusOK, response.Code)
   646  
   647  	status, err := mgr.Read("foo")
   648  	require.NoError(t, err)
   649  
   650  	assert.Equal(t, status.Config().Input.Nanomsg.PollTimeout, "5s")
   651  }
   652  
   653  func TestTypeAPILinting(t *testing.T) {
   654  	mgr := manager.New(
   655  		manager.OptSetLogger(log.Noop()),
   656  		manager.OptSetStats(metrics.Noop()),
   657  		manager.OptSetManager(types.DudMgr{}),
   658  		manager.OptSetAPITimeout(time.Millisecond*100),
   659  	)
   660  
   661  	r := router(mgr)
   662  
   663  	body := []byte(`{
   664  	"input": {
   665  		"type":"nanomsg",
   666  		"file": {}
   667  	},
   668  	"output": {
   669  		"nanomsg": {}
   670  	},
   671  	"cache_resources": [
   672  		{"label":"not_interested","memory":{}}
   673  	]
   674  }`)
   675  
   676  	request, err := http.NewRequest("POST", "/streams/foo", bytes.NewReader(body))
   677  	require.NoError(t, err)
   678  
   679  	response := httptest.NewRecorder()
   680  	r.ServeHTTP(response, request)
   681  	assert.Equal(t, http.StatusBadRequest, response.Code)
   682  
   683  	expLints := `{"lint_errors":["line 4: field file is invalid when the component type is nanomsg (input)","line 9: field cache_resources not recognised"]}`
   684  	assert.Equal(t, expLints, response.Body.String())
   685  
   686  	request, err = http.NewRequest("POST", "/streams/foo?chilled=true", bytes.NewReader(body))
   687  	require.NoError(t, err)
   688  
   689  	response = httptest.NewRecorder()
   690  	r.ServeHTTP(response, request)
   691  	assert.Equal(t, http.StatusOK, response.Code)
   692  }
   693  
   694  func TestResourceAPILinting(t *testing.T) {
   695  	tests := []struct {
   696  		name   string
   697  		ctype  string
   698  		config string
   699  		lints  []string
   700  	}{
   701  		{
   702  			name:  "cache bad",
   703  			ctype: "cache",
   704  			config: `memory:
   705    ttl: 123
   706    nope: nah
   707    compaction_interval: 1s`,
   708  			lints: []string{
   709  				"line 3: field nope not recognised",
   710  			},
   711  		},
   712  		{
   713  			name:  "input bad",
   714  			ctype: "input",
   715  			config: `http_server:
   716    path: /foo/bar
   717    nope: nah`,
   718  			lints: []string{
   719  				"line 3: field nope not recognised",
   720  			},
   721  		},
   722  		{
   723  			name:  "output bad",
   724  			ctype: "output",
   725  			config: `http_server:
   726    path: /foo/bar
   727    nope: nah`,
   728  			lints: []string{
   729  				"line 3: field nope not recognised",
   730  			},
   731  		},
   732  		{
   733  			name:  "processor bad",
   734  			ctype: "processor",
   735  			config: `split:
   736    size: 10
   737    nope: nah`,
   738  			lints: []string{
   739  				"line 3: field nope not recognised",
   740  			},
   741  		},
   742  		{
   743  			name:  "rate limit bad",
   744  			ctype: "rate_limit",
   745  			config: `local:
   746    count: 10
   747    nope: nah`,
   748  			lints: []string{
   749  				"line 3: field nope not recognised",
   750  			},
   751  		},
   752  	}
   753  
   754  	for _, test := range tests {
   755  		t.Run(test.name, func(t *testing.T) {
   756  			bmgr, err := bmanager.NewV2(bmanager.NewResourceConfig(), types.DudMgr{}, log.Noop(), metrics.Noop())
   757  			require.NoError(t, err)
   758  
   759  			mgr := manager.New(
   760  				manager.OptSetLogger(log.Noop()),
   761  				manager.OptSetStats(metrics.Noop()),
   762  				manager.OptSetManager(bmgr),
   763  				manager.OptSetAPITimeout(time.Millisecond*100),
   764  			)
   765  
   766  			r := router(mgr)
   767  
   768  			url := fmt.Sprintf("/resources/%v/foo", test.ctype)
   769  			body := []byte(test.config)
   770  
   771  			request, err := http.NewRequest("POST", url, bytes.NewReader(body))
   772  			require.NoError(t, err)
   773  
   774  			response := httptest.NewRecorder()
   775  			r.ServeHTTP(response, request)
   776  			assert.Equal(t, http.StatusBadRequest, response.Code)
   777  
   778  			expLints, err := json.Marshal(struct {
   779  				LintErrors []string `json:"lint_errors"`
   780  			}{
   781  				LintErrors: test.lints,
   782  			})
   783  			require.NoError(t, err)
   784  
   785  			assert.Equal(t, string(expLints), response.Body.String())
   786  
   787  			request, err = http.NewRequest("POST", url+"?chilled=true", bytes.NewReader(body))
   788  			require.NoError(t, err)
   789  
   790  			response = httptest.NewRecorder()
   791  			r.ServeHTTP(response, request)
   792  			assert.Equal(t, http.StatusOK, response.Code)
   793  		})
   794  	}
   795  }
   796  
   797  func TestTypeAPIGetStats(t *testing.T) {
   798  	mgr, err := bmanager.NewV2(bmanager.NewResourceConfig(), types.DudMgr{}, log.Noop(), metrics.Noop())
   799  	require.NoError(t, err)
   800  
   801  	smgr := manager.New(
   802  		manager.OptSetLogger(log.Noop()),
   803  		manager.OptSetStats(metrics.Noop()),
   804  		manager.OptSetManager(mgr),
   805  		manager.OptSetAPITimeout(time.Millisecond*100),
   806  	)
   807  
   808  	r := router(smgr)
   809  
   810  	err = smgr.Create("foo", harmlessConf())
   811  	require.NoError(t, err)
   812  
   813  	<-time.After(time.Millisecond * 100)
   814  
   815  	request := genRequest("GET", "/streams/not_exist/stats", nil)
   816  	response := httptest.NewRecorder()
   817  	r.ServeHTTP(response, request)
   818  	assert.Equal(t, http.StatusNotFound, response.Code)
   819  
   820  	request = genRequest("POST", "/streams/foo/stats", nil)
   821  	response = httptest.NewRecorder()
   822  	r.ServeHTTP(response, request)
   823  	assert.Equal(t, http.StatusBadRequest, response.Code)
   824  
   825  	request = genRequest("GET", "/streams/foo/stats", nil)
   826  	response = httptest.NewRecorder()
   827  	r.ServeHTTP(response, request)
   828  	assert.Equal(t, http.StatusOK, response.Code)
   829  
   830  	stats, err := gabs.ParseJSON(response.Body.Bytes())
   831  	require.NoError(t, err)
   832  
   833  	assert.Equal(t, 1.0, stats.S("input", "running").Data(), response.Body.String())
   834  }
   835  
   836  func TestTypeAPISetResources(t *testing.T) {
   837  	bmgr, err := bmanager.NewV2(bmanager.NewResourceConfig(), types.DudMgr{}, log.Noop(), metrics.Noop())
   838  	require.NoError(t, err)
   839  
   840  	tChan := make(chan types.Transaction)
   841  	bmgr.SetPipe("feed_in", tChan)
   842  
   843  	mgr := manager.New(
   844  		manager.OptSetLogger(log.Noop()),
   845  		manager.OptSetStats(metrics.Noop()),
   846  		manager.OptSetManager(bmgr),
   847  		manager.OptSetAPITimeout(time.Millisecond*100),
   848  	)
   849  
   850  	tmpDir := t.TempDir()
   851  
   852  	dir1 := filepath.Join(tmpDir, "dir1")
   853  	require.NoError(t, os.MkdirAll(dir1, 0o750))
   854  
   855  	dir2 := filepath.Join(tmpDir, "dir2")
   856  	require.NoError(t, os.MkdirAll(dir2, 0o750))
   857  
   858  	r := router(mgr)
   859  
   860  	cacheConf := cache.NewConfig()
   861  	cacheConf.Type = cache.TypeFile
   862  	cacheConf.File.Directory = dir1
   863  
   864  	request := genYAMLRequest("POST", "/resources/cache/foocache?chilled=true", cacheConf)
   865  	response := httptest.NewRecorder()
   866  	r.ServeHTTP(response, request)
   867  	assert.Equal(t, http.StatusOK, response.Code, response.Body.String())
   868  
   869  	streamConf := stream.NewConfig()
   870  	streamConf.Input.Type = input.TypeInproc
   871  	streamConf.Input.Inproc = "feed_in"
   872  	streamConf.Output.Type = output.TypeCache
   873  	streamConf.Output.Cache.Key = `${! json("id") }`
   874  	streamConf.Output.Cache.Target = "foocache"
   875  
   876  	request = genYAMLRequest("POST", "/streams/foo?chilled=true", streamConf)
   877  	response = httptest.NewRecorder()
   878  	r.ServeHTTP(response, request)
   879  	assert.Equal(t, http.StatusOK, response.Code, response.Body.String())
   880  
   881  	resChan := make(chan types.Response)
   882  	select {
   883  	case tChan <- types.NewTransaction(message.New([][]byte{[]byte(`{"id":"first","content":"hello world"}`)}), resChan):
   884  	case <-time.After(time.Second * 5):
   885  		t.Fatal("timed out")
   886  	}
   887  	select {
   888  	case <-resChan:
   889  	case <-time.After(time.Second * 5):
   890  		t.Fatal("timed out")
   891  	}
   892  
   893  	cacheConf.File.Directory = dir2
   894  
   895  	request = genYAMLRequest("POST", "/resources/cache/foocache?chilled=true", cacheConf)
   896  	response = httptest.NewRecorder()
   897  	r.ServeHTTP(response, request)
   898  	assert.Equal(t, http.StatusOK, response.Code, response.Body.String())
   899  
   900  	select {
   901  	case tChan <- types.NewTransaction(message.New([][]byte{[]byte(`{"id":"second","content":"hello world 2"}`)}), resChan):
   902  	case <-time.After(time.Second * 5):
   903  		t.Fatal("timed out")
   904  	}
   905  	select {
   906  	case <-resChan:
   907  	case <-time.After(time.Second * 5):
   908  		t.Fatal("timed out")
   909  	}
   910  
   911  	files, err := os.ReadDir(dir1)
   912  	require.NoError(t, err)
   913  	assert.Len(t, files, 1)
   914  
   915  	file1Bytes, err := os.ReadFile(filepath.Join(dir1, "first"))
   916  	require.NoError(t, err)
   917  	assert.Equal(t, `{"id":"first","content":"hello world"}`, string(file1Bytes))
   918  
   919  	files, err = os.ReadDir(dir2)
   920  	require.NoError(t, err)
   921  	assert.Len(t, files, 1)
   922  
   923  	file2Bytes, err := os.ReadFile(filepath.Join(dir2, "second"))
   924  	require.NoError(t, err)
   925  	assert.Equal(t, `{"id":"second","content":"hello world 2"}`, string(file2Bytes))
   926  }