How to use writeJSON method of httpmultibin Package

Best K6 code snippet using httpmultibin.writeJSON

logs_test.go

Source:logs_test.go Github

copy

Full Screen

1/*2 *3 * k6 - a next-generation load testing tool4 * Copyright (C) 2020 Load Impact5 *6 * This program is free software: you can redistribute it and/or modify7 * it under the terms of the GNU Affero General Public License as8 * published by the Free Software Foundation, either version 3 of the9 * License, or (at your option) any later version.10 *11 * This program is distributed in the hope that it will be useful,12 * but WITHOUT ANY WARRANTY; without even the implied warranty of13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the14 * GNU Affero General Public License for more details.15 *16 * You should have received a copy of the GNU Affero General Public License17 * along with this program. If not, see <http://www.gnu.org/licenses/>.18 *19 */20package cloudapi21import (22 "context"23 "encoding/json"24 "fmt"25 "io/ioutil"26 "net/http"27 "net/url"28 "strconv"29 "strings"30 "sync/atomic"31 "testing"32 "time"33 "github.com/gorilla/websocket"34 "github.com/mailru/easyjson"35 "github.com/sirupsen/logrus"36 "github.com/stretchr/testify/assert"37 "github.com/stretchr/testify/require"38 "gopkg.in/guregu/null.v3"39 "go.k6.io/k6/lib/testutils"40 "go.k6.io/k6/lib/testutils/httpmultibin"41)42func TestMsgParsing(t *testing.T) {43 m := `{44 "streams": [45 {46 "stream": {47 "key1": "value1",48 "key2": "value2"49 },50 "values": [51 [52 "1598282752000000000",53 "something to log"54 ]55 ]56 }57 ],58 "dropped_entries": [59 {60 "labels": {61 "key3": "value1",62 "key4": "value2"63 },64 "timestamp": "1598282752000000000"65 }66 ]67}68`69 expectMsg := msg{70 Streams: []msgStreams{71 {72 Stream: map[string]string{"key1": "value1", "key2": "value2"},73 Values: [][2]string{{"1598282752000000000", "something to log"}},74 },75 },76 DroppedEntries: []msgDroppedEntries{77 {78 Labels: map[string]string{"key3": "value1", "key4": "value2"},79 Timestamp: "1598282752000000000",80 },81 },82 }83 var message msg84 require.NoError(t, easyjson.Unmarshal([]byte(m), &message))85 require.Equal(t, expectMsg, message)86}87func TestMSGLog(t *testing.T) {88 expectMsg := msg{89 Streams: []msgStreams{90 {91 Stream: map[string]string{"key1": "value1", "key2": "value2"},92 Values: [][2]string{{"1598282752000000000", "something to log"}},93 },94 {95 Stream: map[string]string{"key1": "value1", "key2": "value2", "level": "warn"},96 Values: [][2]string{{"1598282752000000000", "something else log"}},97 },98 },99 DroppedEntries: []msgDroppedEntries{100 {101 Labels: map[string]string{"key3": "value1", "key4": "value2", "level": "panic"},102 Timestamp: "1598282752000000000",103 },104 },105 }106 logger := logrus.New()107 logger.Out = ioutil.Discard108 hook := &testutils.SimpleLogrusHook{HookedLevels: logrus.AllLevels}109 logger.AddHook(hook)110 expectMsg.Log(logger)111 logLines := hook.Drain()112 assert.Equal(t, 4, len(logLines))113 expectTime := time.Unix(0, 1598282752000000000)114 for i, entry := range logLines {115 var expectedMsg string116 switch i {117 case 0:118 expectedMsg = "something to log"119 case 1:120 expectedMsg = "last message had unknown level "121 case 2:122 expectedMsg = "something else log"123 case 3:124 expectedMsg = "dropped"125 }126 require.Equal(t, expectedMsg, entry.Message)127 require.Equal(t, expectTime, entry.Time)128 }129}130func TestRetry(t *testing.T) {131 t.Parallel()132 t.Run("Success", func(t *testing.T) {133 t.Parallel()134 tests := []struct {135 name string136 attempts int137 expWaits []time.Duration // pow(abs(interval), attempt index)138 }{139 {140 name: "NoRetry",141 attempts: 1,142 },143 {144 name: "TwoAttempts",145 attempts: 2,146 expWaits: []time.Duration{5 * time.Second},147 },148 {149 name: "MaximumExceeded",150 attempts: 4,151 expWaits: []time.Duration{5 * time.Second, 25 * time.Second, 2 * time.Minute},152 },153 {154 name: "AttemptsLimit",155 attempts: 5,156 expWaits: []time.Duration{5 * time.Second, 25 * time.Second, 2 * time.Minute, 2 * time.Minute},157 },158 }159 for _, tt := range tests {160 t.Run(tt.name, func(t *testing.T) {161 var sleepRequests []time.Duration162 // sleepCollector tracks the request duration value for sleep requests.163 sleepCollector := sleeperFunc(func(d time.Duration) {164 sleepRequests = append(sleepRequests, d)165 })166 var iterations int167 err := retry(sleepCollector, 5, 5*time.Second, 2*time.Minute, func() error {168 iterations++169 if iterations < tt.attempts {170 return fmt.Errorf("unexpected error")171 }172 return nil173 })174 require.NoError(t, err)175 require.Equal(t, tt.attempts, iterations)176 require.Equal(t, len(tt.expWaits), len(sleepRequests))177 // the added random milliseconds makes difficult to know the exact value178 // so it asserts that expwait <= actual <= expwait + 1s179 for i, expwait := range tt.expWaits {180 assert.GreaterOrEqual(t, sleepRequests[i], expwait)181 assert.LessOrEqual(t, sleepRequests[i], expwait+(1*time.Second))182 }183 })184 }185 })186 t.Run("Fail", func(t *testing.T) {187 t.Parallel()188 mock := sleeperFunc(func(time.Duration) { /* noop - nowait */ })189 err := retry(mock, 5, 5*time.Second, 30*time.Second, func() error {190 return fmt.Errorf("unexpected error")191 })192 assert.Error(t, err, "unexpected error")193 })194}195func TestStreamLogsToLogger(t *testing.T) {196 t.Parallel()197 // It registers an handler for the logtail endpoint198 // It upgrades as websocket the HTTP handler and invokes the provided callback.199 logtailHandleFunc := func(tb *httpmultibin.HTTPMultiBin, fn func(*websocket.Conn, *http.Request)) {200 upgrader := websocket.Upgrader{201 ReadBufferSize: 1024,202 WriteBufferSize: 1024,203 }204 tb.Mux.HandleFunc("/api/v1/tail", func(w http.ResponseWriter, req *http.Request) {205 conn, err := upgrader.Upgrade(w, req, nil)206 require.NoError(t, err)207 fn(conn, req)208 _ = conn.Close()209 })210 }211 // a basic config with the logtail endpoint set212 configFromHTTPMultiBin := func(tb *httpmultibin.HTTPMultiBin) Config {213 wsurl := strings.TrimPrefix(tb.ServerHTTP.URL, "http://")214 return Config{215 LogsTailURL: null.NewString(fmt.Sprintf("ws://%s/api/v1/tail", wsurl), false),216 }217 }218 // get all messages from the mocked logger219 logLines := func(hook *testutils.SimpleLogrusHook) (lines []string) {220 for _, e := range hook.Drain() {221 lines = append(lines, e.Message)222 }223 return224 }225 generateLogline := func(key string, ts uint64, msg string) string {226 return fmt.Sprintf(`{"streams":[{"stream":{"key":%q,"level":"warn"},"values":[["%d",%q]]}],"dropped_entities":[]}`, key, ts, msg)227 }228 t.Run("Success", func(t *testing.T) {229 t.Parallel()230 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)231 defer cancel()232 tb := httpmultibin.NewHTTPMultiBin(t)233 logtailHandleFunc(tb, func(conn *websocket.Conn, _ *http.Request) {234 rawmsg := json.RawMessage(generateLogline("stream1", 1598282752000000000, "logline1"))235 err := conn.WriteJSON(rawmsg)236 require.NoError(t, err)237 rawmsg = json.RawMessage(generateLogline("stream2", 1598282752000000001, "logline2"))238 err = conn.WriteJSON(rawmsg)239 require.NoError(t, err)240 // wait the flush on the network241 time.Sleep(5 * time.Millisecond)242 cancel()243 })244 logger := logrus.New()245 logger.Out = ioutil.Discard246 hook := &testutils.SimpleLogrusHook{HookedLevels: logrus.AllLevels}247 logger.AddHook(hook)248 c := configFromHTTPMultiBin(tb)249 err := c.StreamLogsToLogger(ctx, logger, "ref_id", 0)250 require.NoError(t, err)251 assert.Equal(t, []string{"logline1", "logline2"}, logLines(hook))252 })253 t.Run("RestoreConnFromLatestMessage", func(t *testing.T) {254 t.Parallel()255 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)256 defer cancel()257 startFilter := func(u url.URL) (start time.Time, err error) {258 rawstart, err := strconv.ParseInt(u.Query().Get("start"), 10, 64)259 if err != nil {260 return start, err261 }262 start = time.Unix(0, rawstart)263 return264 }265 var requestsCount uint64266 tb := httpmultibin.NewHTTPMultiBin(t)267 logtailHandleFunc(tb, func(conn *websocket.Conn, req *http.Request) {268 requests := atomic.AddUint64(&requestsCount, 1)269 start, err := startFilter(*req.URL)270 require.NoError(t, err)271 if requests <= 1 {272 t0 := time.Date(2021, time.July, 27, 0, 0, 0, 0, time.UTC).UnixNano()273 t1 := time.Date(2021, time.July, 27, 1, 0, 0, 0, time.UTC).UnixNano()274 t2 := time.Date(2021, time.July, 27, 2, 0, 0, 0, time.UTC).UnixNano()275 // send a correct logline so we will able to assert276 // that the connection is restored from t2 as expected277 rawmsg := json.RawMessage(fmt.Sprintf(`{"streams":[{"stream":{"key":"stream1","level":"warn"},"values":[["%d","newest logline"],["%d","second logline"],["%d","oldest logline"]]}],"dropped_entities":[]}`, t2, t1, t0))278 err = conn.WriteJSON(rawmsg)279 require.NoError(t, err)280 // wait the flush of the message on the network281 time.Sleep(20 * time.Millisecond)282 // it generates a failure closing the connection283 // in a rude way284 err = conn.Close()285 require.NoError(t, err)286 return287 }288 // assert that the client created the request with `start`289 // populated from the most recent seen value (t2+1ns)290 require.Equal(t, time.Unix(0, 1627351200000000001), start)291 // send a correct logline so we will able to assert292 // that the connection is restored as expected293 err = conn.WriteJSON(json.RawMessage(generateLogline("stream3", 1627358400000000000, "logline-after-restored-conn")))294 require.NoError(t, err)295 // wait the flush of the message on the network296 time.Sleep(20 * time.Millisecond)297 cancel()298 })299 logger := logrus.New()300 logger.Out = ioutil.Discard301 hook := &testutils.SimpleLogrusHook{HookedLevels: logrus.AllLevels}302 logger.AddHook(hook)303 c := configFromHTTPMultiBin(tb)304 err := c.StreamLogsToLogger(ctx, logger, "ref_id", 0)305 require.NoError(t, err)306 assert.Equal(t,307 []string{308 "newest logline",309 "second logline",310 "oldest logline",311 "error reading a log message from the cloud, trying to establish a fresh connection with the logs service...",312 "logline-after-restored-conn",313 }, logLines(hook))314 })315 t.Run("RestoreConnFromTimeNow", func(t *testing.T) {316 t.Parallel()317 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)318 defer cancel()319 startFilter := func(u url.URL) (start time.Time, err error) {320 rawstart, err := strconv.ParseInt(u.Query().Get("start"), 10, 64)321 if err != nil {322 return start, err323 }324 start = time.Unix(0, rawstart)325 return326 }327 var requestsCount uint64328 t0 := time.Now()329 tb := httpmultibin.NewHTTPMultiBin(t)330 logtailHandleFunc(tb, func(conn *websocket.Conn, req *http.Request) {331 requests := atomic.AddUint64(&requestsCount, 1)332 start, err := startFilter(*req.URL)333 require.NoError(t, err)334 if requests <= 1 {335 // if it's the first attempt then336 // it generates a failure closing the connection337 // in a rude way338 err = conn.Close()339 require.NoError(t, err)340 return341 }342 // it asserts that the second attempt343 // has a `start` after the test run344 require.True(t, start.After(t0))345 // send a correct logline so we will able to assert346 // that the connection is restored as expected347 err = conn.WriteJSON(json.RawMessage(`{"streams":[{"stream":{"key":"stream1","level":"warn"},"values":[["1598282752000000000","logline-after-restored-conn"]]}],"dropped_entities":[]}`))348 require.NoError(t, err)349 // wait the flush of the message on the network350 time.Sleep(20 * time.Millisecond)351 cancel()352 })353 logger := logrus.New()354 logger.Out = ioutil.Discard355 hook := &testutils.SimpleLogrusHook{HookedLevels: logrus.AllLevels}356 logger.AddHook(hook)357 c := configFromHTTPMultiBin(tb)358 err := c.StreamLogsToLogger(ctx, logger, "ref_id", 0)359 require.NoError(t, err)360 assert.Equal(t,361 []string{362 "error reading a log message from the cloud, trying to establish a fresh connection with the logs service...",363 "logline-after-restored-conn",364 }, logLines(hook))365 })366}...

Full Screen

Full Screen

httpmultibin.go

Source:httpmultibin.go Github

copy

Full Screen

...124 return125 }126 })127}128func writeJSON(w io.Writer, v interface{}) error {129 e := json.NewEncoder(w)130 e.SetIndent("", " ")131 return errors.Wrap(e.Encode(v), "failed to encode JSON")132}133func getEncodedHandler(t testing.TB, compressionType httpext.CompressionType) http.Handler {134 return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {135 var (136 encoding string137 err error138 encw io.WriteCloser139 )140 switch compressionType {141 case httpext.CompressionTypeBr:142 encw = brotli.NewWriter(rw)143 encoding = "br"144 case httpext.CompressionTypeZstd:145 encw, _ = zstd.NewWriter(rw)146 encoding = "zstd"147 }148 rw.Header().Set("Content-Type", "application/json")149 rw.Header().Add("Content-Encoding", encoding)150 data := jsonBody{151 Header: req.Header,152 Compression: encoding,153 }154 err = writeJSON(encw, data)155 if encw != nil {156 _ = encw.Close()157 }158 if !assert.NoError(t, err) {159 return160 }161 })162}163func getZstdBrHandler(t testing.TB) http.Handler {164 return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {165 encoding := "zstd, br"166 rw.Header().Set("Content-Type", "application/json")167 rw.Header().Add("Content-Encoding", encoding)168 data := jsonBody{169 Header: req.Header,170 Compression: encoding,171 }172 bw := brotli.NewWriter(rw)173 zw, _ := zstd.NewWriter(bw)174 defer func() {175 _ = zw.Close()176 _ = bw.Close()177 }()178 require.NoError(t, writeJSON(zw, data))179 })180}181// NewHTTPMultiBin returns a fully configured and running HTTPMultiBin182func NewHTTPMultiBin(t testing.TB) *HTTPMultiBin {183 // Create a http.ServeMux and set the httpbin handler as the default184 mux := http.NewServeMux()185 mux.Handle("/brotli", getEncodedHandler(t, httpext.CompressionTypeBr))186 mux.Handle("/ws-echo", getWebsocketHandler(true, false))187 mux.Handle("/ws-echo-invalid", getWebsocketHandler(true, true))188 mux.Handle("/ws-close", getWebsocketHandler(false, false))189 mux.Handle("/ws-close-invalid", getWebsocketHandler(false, true))190 mux.Handle("/zstd", getEncodedHandler(t, httpext.CompressionTypeZstd))191 mux.Handle("/zstd-br", getZstdBrHandler(t))192 mux.Handle("/", httpbin.New().Handler())...

Full Screen

Full Screen

writeJSON

Using AI Code Generation

copy

Full Screen

1import (2func TestServer(t *testing.T) {3 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {4 fmt.Fprintln(w, "Hello, client")5 }))6 defer ts.Close()7}8func TestWriteJSON(t *testing.T) {9 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {10 httpmultibin.WriteJSON(w, map[string]string{"hello": "world"})11 }))12 defer ts.Close()13}14import (15func TestServer(t *testing.T) {16 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {17 fmt.Fprintln(w, "Hello, client")18 }))19 defer ts.Close()20}21func TestWriteJSON(t *testing.T) {22 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {23 httpmultibin.WriteJSON(w, map[string]string{"hello": "world"})24 }))25 defer ts.Close()26}27import (28func TestServer(t *testing.T) {29 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {30 fmt.Fprintln(w, "Hello, client")31 }))32 defer ts.Close()33}34func TestWriteJSON(t *testing.T) {35 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {36 httpmultibin.WriteJSON(w, map[string]string{"hello": "world"})37 }))38 defer ts.Close()39}40import (41func TestServer(t *testing.T) {42 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

Full Screen

Full Screen

writeJSON

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 client := http.Client{4 }5 if err != nil {6 fmt.Println(err)7 }8 fmt.Println(resp)9}10import (11func main() {12 client := http.Client{13 }14 if err != nil {15 fmt.Println(err)16 }17 fmt.Println(resp)18}19import (20func main() {21 client := http.Client{22 }23 if err != nil {24 fmt.Println(err)25 }26 fmt.Println(resp)27}28import (29func main() {30 client := http.Client{31 }32 if err != nil {33 fmt.Println(err)34 }35 fmt.Println(resp)36}37import (38func main() {39 client := http.Client{40 }41 if err != nil {42 fmt.Println(err)43 }44 fmt.Println(resp)45}46import (47func main() {48 client := http.Client{49 }

Full Screen

Full Screen

writeJSON

Using AI Code Generation

copy

Full Screen

1import (2func TestWriteJSON(t *testing.T) {3 mux := http.NewServeMux()4 mux.HandleFunc("/writeJSON", func(w http.ResponseWriter, r *http.Request) {5 httpmultibin.WriteJSON(w, map[string]string{"hello": "world"})6 })7 server := httptest.NewServer(mux)8 defer server.Close()9 resp, err := http.Get(server.URL + "/writeJSON")10 if err != nil {11 t.Fatal(err)12 }13 defer resp.Body.Close()14 if resp.StatusCode != http.StatusOK {15 t.Errorf("expected status OK; got %

Full Screen

Full Screen

writeJSON

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 http2Server = &http2.Server{4 }5 mux := http.NewServeMux()6 mux.Handle("/writeJSON", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {7 w.Write([]byte(`{"foo": "bar"}`))8 }))9 mux.Handle("/writeJSON2", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {10 w.Write([]byte(`{"foo": "bar"}`))11 }))12 mux.Handle("/writeJSON3", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {13 w.Write([]byte(`{"foo": "bar"}`))14 }))15 mux.Handle("/writeJSON4", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {16 w.Write([]byte(`{"foo": "bar"}`))17 }))18 mux.Handle("/writeJSON5", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {19 w.Write([]byte(`{"foo": "bar"}`))20 }))21 mux.Handle("/writeJSON6", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {22 w.Write([]byte(`{"foo": "bar"}`))23 }))24 mux.Handle("/writeJSON7", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {25 w.Write([]byte(`{"foo": "bar"}`))26 }))27 mux.Handle("/writeJSON8", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {28 w.Write([]byte(`{"foo": "bar"}`))29 }))30 mux.Handle("/writeJSON9", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {31 w.Write([]byte(`{"foo": "bar"}`))32 }))33 mux.Handle("/writeJSON10", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

Full Screen

Full Screen

writeJSON

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 httpmultibin := &httpmultibin{}4 httpmultibin.writeJSON()5}6type httpmultibin struct{}7func (httpmultibin *httpmultibin) writeJSON() {8 client := &http.Client{}9 if err != nil {10 fmt.Println(err)11 }12 req.Header.Add("Content-Type", "application/json")13 resp, err := client.Do(req)14 if err != nil {15 fmt.Println(err)16 }17 respBody, err := ioutil.ReadAll(resp.Body)18 if err != nil {19 fmt.Println(err)20 }21 fmt.Println(string(respBody))22 resp.Body.Close()23}24import (25func main() {26 httpmultibin := &httpmultibin{}27 httpmultibin.writeJSON()28}29type httpmultibin struct{}30func (httpmultibin *httpmultibin) writeJSON() {31 client := &http.Client{}32 if err != nil {33 fmt.Println(err)34 }35 req.Header.Add("Content-Type", "application/json")36 resp, err := client.Do(req)37 if err != nil {38 fmt.Println(err)39 }40 respBody, err := ioutil.ReadAll(resp.Body)41 if err != nil {42 fmt.Println(err)43 }44 fmt.Println(string(respBody))45 resp.Body.Close()46}

Full Screen

Full Screen

writeJSON

Using AI Code Generation

copy

Full Screen

1import (2type httpmultibin struct {3}4func (h *httpmultibin) writeJSON(w http.ResponseWriter, r *http.Request) {5 w.Header().Set("Content-Type", "application/json")6 w.Write([]byte(`{"id":1,"name":"John"}`))7}8func (h *httpmultibin) readJSON(w http.ResponseWriter, r *http.Request) {9 fmt.Println(r.Body)10}11func (h *httpmultibin) get(w http.ResponseWriter, r *http.Request) {12 fmt.Println("get method")13}14func main() {15 mux := http.NewServeMux()16 h := &httpmultibin{}17 mux.HandleFunc("/json", h.writeJSON)18 mux.HandleFunc("/read", h.readJSON)19 mux.HandleFunc("/get", h.get)20 http.ListenAndServe(":8080", mux)21}22{1 John}

Full Screen

Full Screen

writeJSON

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 h := httpmultibin.New("localhost:8080")4 ts := httptest.NewServer(h)5 h.WriteJSON(ts.URL, "hello", map[string]string{6 })7 resp, err := http.Get(ts.URL)8 if err != nil {9 fmt.Println(err)10 }11 fmt.Println(resp)12}13{200 OK 200 HTTP/1.1 1 1 map[Content-Type:[appli

Full Screen

Full Screen

Automation Testing Tutorials

Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful