From 42a295da31674147c0c10eb19ec8b95e85e8a52a Mon Sep 17 00:00:00 2001 From: Evgenii Shuvalov Date: Thu, 25 Jul 2024 16:24:53 +0300 Subject: [PATCH] iter9 features --- db | 1 - db2 | 1 - internal/compression/compression_test.go | 31 ++++ internal/compression/compressor.go | 2 + internal/compression/compressor_test.go | 97 ++++++++++ internal/compression/decompressor.go | 2 + internal/compression/decompressor_test.go | 130 ++++++++++++++ internal/middleware/middleware_test.go | 204 ++++++++++++++++++++++ internal/storage/file_test.go | 85 +++++++++ internal/storage/record.go | 12 +- internal/storage/record_test.go | 55 ++++++ internal/utils/utils_test.go | 21 +++ 12 files changed, 631 insertions(+), 10 deletions(-) delete mode 100644 db delete mode 100644 db2 create mode 100644 internal/compression/compression_test.go create mode 100644 internal/compression/compressor_test.go create mode 100644 internal/compression/decompressor_test.go create mode 100644 internal/middleware/middleware_test.go create mode 100644 internal/storage/file_test.go diff --git a/db b/db deleted file mode 100644 index e61643c..0000000 --- a/db +++ /dev/null @@ -1 +0,0 @@ -{"records":{"Alloc_gauge":{"kind":"gauge","name":"Alloc","value":"1548608"},"BuckHashSys_gauge":{"kind":"gauge","name":"BuckHashSys","value":"7332"},"Frees_gauge":{"kind":"gauge","name":"Frees","value":"8253"},"GCCPUFraction_gauge":{"kind":"gauge","name":"GCCPUFraction","value":"0.0002756141105024748"},"GCSys_gauge":{"kind":"gauge","name":"GCSys","value":"2361592"},"HeapAlloc_gauge":{"kind":"gauge","name":"HeapAlloc","value":"1548608"},"HeapIdle_gauge":{"kind":"gauge","name":"HeapIdle","value":"5398528"},"HeapInuse_gauge":{"kind":"gauge","name":"HeapInuse","value":"2375680"},"HeapObjects_gauge":{"kind":"gauge","name":"HeapObjects","value":"895"},"HeapReleased_gauge":{"kind":"gauge","name":"HeapReleased","value":"2842624"},"HeapSys_gauge":{"kind":"gauge","name":"HeapSys","value":"7774208"},"LastGC_gauge":{"kind":"gauge","name":"LastGC","value":"1721906462828472000"},"Lookups_gauge":{"kind":"gauge","name":"Lookups","value":"0"},"MCacheInuse_gauge":{"kind":"gauge","name":"MCacheInuse","value":"9600"},"MCacheSys_gauge":{"kind":"gauge","name":"MCacheSys","value":"15600"},"MSpanInuse_gauge":{"kind":"gauge","name":"MSpanInuse","value":"82720"},"MSpanSys_gauge":{"kind":"gauge","name":"MSpanSys","value":"97920"},"Mallocs_gauge":{"kind":"gauge","name":"Mallocs","value":"9148"},"NextGC_gauge":{"kind":"gauge","name":"NextGC","value":"4194304"},"NumForcedGC_gauge":{"kind":"gauge","name":"NumForcedGC","value":"0"},"NumGC_gauge":{"kind":"gauge","name":"NumGC","value":"14"},"OtherSys_gauge":{"kind":"gauge","name":"OtherSys","value":"969476"},"PauseTotalNs_gauge":{"kind":"gauge","name":"PauseTotalNs","value":"399125"},"PollCount_counter":{"kind":"counter","name":"PollCount","value":"10"},"RandomValue_gauge":{"kind":"gauge","name":"RandomValue","value":"0.5895582908291291"},"StackInuse_gauge":{"kind":"gauge","name":"StackInuse","value":"589824"},"StackSys_gauge":{"kind":"gauge","name":"StackSys","value":"589824"},"Sys_gauge":{"kind":"gauge","name":"Sys","value":"11815952"},"TotalAlloc_gauge":{"kind":"gauge","name":"TotalAlloc","value":"37623104"}}} diff --git a/db2 b/db2 deleted file mode 100644 index e2f2276..0000000 --- a/db2 +++ /dev/null @@ -1 +0,0 @@ -{"records":{}} diff --git a/internal/compression/compression_test.go b/internal/compression/compression_test.go new file mode 100644 index 0000000..ec23374 --- /dev/null +++ b/internal/compression/compression_test.go @@ -0,0 +1,31 @@ +package compression + +import ( + "bytes" + "compress/gzip" + "io" + "testing" +) + +func TestPack_Success(t *testing.T) { + data := []byte("test data") + buffer, err := Pack(data) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + reader, err := gzip.NewReader(buffer) + if err != nil { + t.Fatalf("expected no error creating gzip reader, got %v", err) + } + defer reader.Close() + + unpackedData, err := io.ReadAll(reader) + if err != nil { + t.Fatalf("expected no error reading from gzip reader, got %v", err) + } + + if !bytes.Equal(data, unpackedData) { + t.Fatalf("expected %s, got %s", data, unpackedData) + } +} diff --git a/internal/compression/compressor.go b/internal/compression/compressor.go index 46ff7a2..6eaec34 100644 --- a/internal/compression/compressor.go +++ b/internal/compression/compressor.go @@ -58,4 +58,6 @@ func (c *Compressor) Close() { if err := c.encoder.Close(); err != nil { logging.LogErrorCtx(c.context, err, "error closing compressor encoder", err.Error()) } + + c.encoder = nil } diff --git a/internal/compression/compressor_test.go b/internal/compression/compressor_test.go new file mode 100644 index 0000000..99b1d0f --- /dev/null +++ b/internal/compression/compressor_test.go @@ -0,0 +1,97 @@ +package compression + +import ( + "bytes" + "compress/gzip" + "context" + "net/http/httptest" + "testing" +) + +func TestCompressor_Write_SupportedContent(t *testing.T) { + ctx := context.Background() + recorder := httptest.NewRecorder() + + compressor := NewCompressor(recorder, ctx) + compressor.Header().Set("Content-Type", "application/json") + + data := []byte(`{"message": "test"}`) + n, err := compressor.Write(data) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if n != len(data) { + t.Fatalf("expected %d bytes written, got %d", len(data), n) + } + + compressor.Close() + + resp := recorder.Result() + defer resp.Body.Close() + + if resp.Header.Get("Content-Encoding") != "gzip" { + t.Fatalf("expected Content-Encoding to be gzip, got %s", resp.Header.Get("Content-Encoding")) + } + + gr, err := gzip.NewReader(resp.Body) + if err != nil { + t.Fatalf("expected no error creating gzip reader, got %v", err) + } + defer gr.Close() + + uncompressedData := new(bytes.Buffer) + _, err = uncompressedData.ReadFrom(gr) + if err != nil { + t.Fatalf("expected no error decompressing dara, got %v", err) + } + if !bytes.Equal(data, uncompressedData.Bytes()) { + t.Fatalf("expected %s, got %s", data, uncompressedData.Bytes()) + } +} + +func TestCompressor_Write_UnsupportedContent(t *testing.T) { + recorder := httptest.NewRecorder() + ctx := context.Background() + compressor := NewCompressor(recorder, ctx) + compressor.Header().Set("Content-Type", "text/plain") + + data := []byte("test data") + n, err := compressor.Write(data) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if n != len(data) { + t.Fatalf("expected %d bytes written, got %d", len(data), n) + } + + resp := recorder.Result() + defer resp.Body.Close() + + if resp.Header.Get("Content-Encoding") == "gzip" { + t.Fatalf("expected Content-Encoding to not be gzip") + } + + body := recorder.Body.Bytes() + if !bytes.Equal(data, body) { + t.Fatalf("expected %s, got %s", data, body) + } +} + +func TestCompressor_Close(t *testing.T) { + recorder := httptest.NewRecorder() + ctx := context.Background() + compressor := NewCompressor(recorder, ctx) + compressor.Header().Set("Content-Type", "application/json") + + data := []byte(`{"message": "test"}`) + _, err := compressor.Write(data) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + compressor.Close() + + if compressor.encoder != nil { + t.Fatalf("expected encoder to be nil after close") + } +} diff --git a/internal/compression/decompressor.go b/internal/compression/decompressor.go index 4b86c10..d9eee9d 100644 --- a/internal/compression/decompressor.go +++ b/internal/compression/decompressor.go @@ -70,4 +70,6 @@ func (d *Decompressor) Close() { if err := d.reader.Close(); err != nil { logging.LogErrorCtx(d.context, err, "error closing decompressor reader", err.Error()) } + + d.reader = nil } diff --git a/internal/compression/decompressor_test.go b/internal/compression/decompressor_test.go new file mode 100644 index 0000000..934cd80 --- /dev/null +++ b/internal/compression/decompressor_test.go @@ -0,0 +1,130 @@ +package compression + +import ( + "bytes" + "compress/gzip" + "context" + "errors" + "io" + + "github.com/ex0rcist/metflix/internal/entities" + + "net/http" + "net/http/httptest" + "testing" +) + +func TestDecompressor_Decompress_SupportedEncoding(t *testing.T) { + data := []byte("test data") + var buf bytes.Buffer + writer := gzip.NewWriter(&buf) + _, err := writer.Write(data) + if err != nil { + t.Fatalf("expected no error on writer.Write(), got %v", err) + } + + writer.Close() + + req := httptest.NewRequest(http.MethodPost, "/", &buf) + req.Header.Set("Content-Encoding", "gzip") + + ctx := context.Background() + decompressor := NewDecompressor(req, ctx) + + err = decompressor.Decompress() + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + decompressedData, err := io.ReadAll(decompressor.request.Body) + if err != nil { + t.Fatalf("expected no error reading decompressed data, got %v", err) + } + + if !bytes.Equal(data, decompressedData) { + t.Fatalf("expected %s, got %s", data, decompressedData) + } + + decompressor.Close() +} + +func TestDecompressor_Decompress_NoEncoding(t *testing.T) { + data := []byte("test data") + req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(data)) + + ctx := context.Background() + decompressor := NewDecompressor(req, ctx) + + err := decompressor.Decompress() + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + decompressedData, err := io.ReadAll(decompressor.request.Body) + if err != nil { + t.Fatalf("expected no error reading data, got %v", err) + } + + if !bytes.Equal(data, decompressedData) { + t.Fatalf("expected %s, got %s", data, decompressedData) + } + + decompressor.Close() +} + +func TestDecompressor_Decompress_UnsupportedEncoding(t *testing.T) { + data := []byte("test data") + req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(data)) + req.Header.Set("Content-Encoding", "deflate") + + ctx := context.Background() + decompressor := NewDecompressor(req, ctx) + + err := decompressor.Decompress() + if !errors.Is(err, entities.ErrEncodingUnsupported) { + t.Fatalf("expected %v, got %v", entities.ErrEncodingUnsupported, err) + } +} + +func TestDecompressor_Close(t *testing.T) { + data := []byte("test data") + var buf bytes.Buffer + writer := gzip.NewWriter(&buf) + _, err := writer.Write(data) + if err != nil { + t.Fatalf("expected no error on writer.Write, got %v", err) + } + writer.Close() + + req := httptest.NewRequest(http.MethodPost, "/", &buf) + req.Header.Set("Content-Encoding", "gzip") + + ctx := context.Background() + decompressor := NewDecompressor(req, ctx) + + err = decompressor.Decompress() + if err != nil { + t.Fatalf("expected no error on Decompress(), got %v", err) + } + + decompressor.Close() + + if decompressor.reader != nil { + t.Fatalf("expected reader to be nil after close") + } +} + +func TestDecompressor_Decompress_ErrorOnInit(t *testing.T) { + // providing invalid gzip data to simulate error on NewReader + data := []byte("invalid gzip data") + req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(data)) + req.Header.Set("Content-Encoding", "gzip") + + ctx := context.Background() + decompressor := NewDecompressor(req, ctx) + + err := decompressor.Decompress() + if !errors.Is(err, entities.ErrEncodingInternal) { + t.Fatalf("expected %v, got %v", entities.ErrEncodingInternal, err) + } +} diff --git a/internal/middleware/middleware_test.go b/internal/middleware/middleware_test.go new file mode 100644 index 0000000..c61105f --- /dev/null +++ b/internal/middleware/middleware_test.go @@ -0,0 +1,204 @@ +package middleware + +import ( + "bytes" + "compress/gzip" + "io" + "net/http" + "net/http/httptest" + + "testing" +) + +func TestDecompressRequest_Success(t *testing.T) { + data := []byte("test data") + var buf bytes.Buffer + writer := gzip.NewWriter(&buf) + _, err := writer.Write(data) + if err != nil { + t.Fatalf("expected no error writing writer.Write(), got %v", err) + } + + writer.Close() + + req := httptest.NewRequest(http.MethodPost, "/", &buf) + req.Header.Set("Content-Encoding", "gzip") + + nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + decompressedData, err := io.ReadAll(r.Body) + if err != nil { + t.Fatalf("expected no error reading decompressed data, got %v", err) + } + if !bytes.Equal(data, decompressedData) { + t.Fatalf("expected %s, got %s", data, decompressedData) + } + }) + + handler := DecompressRequest(nextHandler) + rr := httptest.NewRecorder() + + handler.ServeHTTP(rr, req) +} + +func TestDecompressRequest_NoEncoding(t *testing.T) { + req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader([]byte("test data"))) + + nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body, _ := io.ReadAll(r.Body) + if string(body) != "test data" { + t.Fatalf("expected 'test data', got %s", string(body)) + } + }) + + handler := DecompressRequest(nextHandler) + rr := httptest.NewRecorder() + + handler.ServeHTTP(rr, req) +} + +func TestDecompressRequest_UnsupportedEncoding(t *testing.T) { + req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader([]byte("test data"))) + req.Header.Set("Content-Encoding", "deflate") + + nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Fatal("expected handler not to be called") + }) + + handler := DecompressRequest(nextHandler) + rr := httptest.NewRecorder() + + handler.ServeHTTP(rr, req) + + if rr.Code != http.StatusBadRequest { + t.Fatalf("expected status %d, got %d", http.StatusBadRequest, rr.Code) + } +} + +func TestDecompressRequest_InternalError(t *testing.T) { + req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader([]byte("invalid gzip data"))) + req.Header.Set("Content-Encoding", "gzip") + + nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Fatal("expected handler not to be called") + }) + + handler := DecompressRequest(nextHandler) + rr := httptest.NewRecorder() + + handler.ServeHTTP(rr, req) + + if rr.Code != http.StatusInternalServerError { + t.Fatalf("expected status %d, got %d", http.StatusInternalServerError, rr.Code) + } +} + +func TestCompressResponse_Success(t *testing.T) { + data := []byte("test data") + + nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _, err := w.Write(data) + + if err != nil { + t.Fatalf("expected no error writing writer.Write(), got %v", err) + } + }) + + req := httptest.NewRequest(http.MethodGet, "/", nil) + req.Header.Set("Accept-Encoding", "gzip") + + rr := httptest.NewRecorder() + handler := CompressResponse(nextHandler) + + handler.ServeHTTP(rr, req) + + resp := rr.Result() + defer resp.Body.Close() + + if resp.Header.Get("Content-Encoding") != "gzip" { + t.Fatalf("expected Content-Encoding to be gzip, got %s", resp.Header.Get("Content-Encoding")) + } + + gr, err := gzip.NewReader(resp.Body) + if err != nil { + t.Fatalf("expected no error creating gzip reader, got %v", err) + } + defer gr.Close() + + decompressedData, err := io.ReadAll(gr) + if err != nil { + t.Fatalf("expected no error reading decompressed data, got %v", err) + } + + if !bytes.Equal(data, decompressedData) { + t.Fatalf("expected %s, got %s", data, decompressedData) + } +} + +func TestCompressResponse_NoCompressionRequested(t *testing.T) { + data := []byte("test data") + + nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write(data) + if err != nil { + t.Fatalf("expected no error writing writer.Write(), got %v", err) + } + }) + + req := httptest.NewRequest(http.MethodGet, "/", nil) + + rr := httptest.NewRecorder() + handler := CompressResponse(nextHandler) + + handler.ServeHTTP(rr, req) + + resp := rr.Result() + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatalf("expected no error reading body, got %v", err) + } + + if !bytes.Equal(data, body) { + t.Fatalf("expected %s, got %s", data, body) + } +} + +// findOrCreateRequestID tests +func TestFindOrCreateRequestID_ExistingID(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "/", nil) + req.Header.Set("X-Request-Id", "existing-id") + + requestID := findOrCreateRequestID(req) + if requestID != "existing-id" { + t.Fatalf("expected 'existing-id', got %s", requestID) + } +} + +func TestFindOrCreateRequestID_NewID(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "/", nil) + + requestID := findOrCreateRequestID(req) + if requestID == "" { + t.Fatalf("expected non-empty request ID") + } +} + +// needGzipEncoding tests +func TestNeedGzipEncoding_Supported(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "/", nil) + req.Header.Set("Accept-Encoding", "gzip") + + if !needGzipEncoding(req) { + t.Fatalf("expected needGzipEncoding to return true") + } +} + +func TestNeedGzipEncoding_Unsupported(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "/", nil) + + if needGzipEncoding(req) { + t.Fatalf("expected needGzipEncoding to return false") + } +} diff --git a/internal/storage/file_test.go b/internal/storage/file_test.go new file mode 100644 index 0000000..3be8edc --- /dev/null +++ b/internal/storage/file_test.go @@ -0,0 +1,85 @@ +package storage + +import ( + "os" + "testing" + + "github.com/ex0rcist/metflix/internal/metrics" + "github.com/stretchr/testify/require" +) + +func createStoreWithData(t *testing.T, storePath string, storeInterval int) *FileStorage { + store := NewFileStorage(storePath, storeInterval) + + err := store.Push("test_gauge", Record{Name: "test", Value: metrics.Gauge(42.42)}) + if err != nil { + t.Fatalf("expected no error on push, got %v", err) + } + + err = store.Push("test2_counter", Record{Name: "test2", Value: metrics.Counter(1)}) + if err != nil { + t.Fatalf("expected no error on push, got %v", err) + } + + return store +} + +func TestSyncDumpRestoreStorage(t *testing.T) { + storePath := "/tmp/db.json" + + t.Cleanup(func() { + err := os.Remove(storePath) + if err != nil { + t.Fatalf("expected no error on cleanup, got %v", err) + } + }) + + store := createStoreWithData(t, storePath, 0) + storedData := store.Snapshot() + + store = NewFileStorage(storePath, 0) + err := store.Restore() + if err != nil { + t.Fatalf("expected no error on restore, got %v", err) + } + + restoredData := store.Snapshot() + require.Equal(t, storedData, restoredData) +} + +func TestAsyncDumpRestoreStorage(t *testing.T) { + storePath := "/tmp/db.json" + + t.Cleanup(func() { + err := os.Remove(storePath) + if err != nil { + t.Fatalf("expected no error on cleanup, got %v", err) + } + }) + + store := createStoreWithData(t, storePath, 300) + storedData := store.Snapshot() + + err := store.Dump() + if err != nil { + t.Fatalf("expected no error on dump, got %v", err) + } + + store = NewFileStorage(storePath, 300) + err = store.Restore() + if err != nil { + t.Fatalf("expected no error on restore, got %v", err) + } + + restoredData := store.Snapshot() + require.Equal(t, storedData, restoredData) +} + +func TestRestoreDoesntFailIfNoSourceFile(t *testing.T) { + store := NewFileStorage("xxx", 0) + + err := store.Restore() + if err != nil { + t.Fatalf("expected no error on empty file, got %v", err) + } +} diff --git a/internal/storage/record.go b/internal/storage/record.go index 9fce565..2cbf6b0 100644 --- a/internal/storage/record.go +++ b/internal/storage/record.go @@ -43,7 +43,7 @@ func (r *Record) UnmarshalJSON(src []byte) error { var data map[string]string if err := json.Unmarshal(src, &data); err != nil { - return unmarshalFailed(err) + return fmt.Errorf("record unmarshaling failed: %w", err) } r.Name = data["name"] @@ -52,24 +52,20 @@ func (r *Record) UnmarshalJSON(src []byte) error { case "counter": value, err := metrics.ToCounter(data["value"]) if err != nil { - return unmarshalFailed(err) + return fmt.Errorf("record unmarshaling failed: %w", err) } r.Value = value case "gauge": value, err := metrics.ToGauge(data["value"]) if err != nil { - return unmarshalFailed(err) + return fmt.Errorf("record unmarshaling failed: %w", err) } r.Value = value default: - return unmarshalFailed(entities.ErrMetricUnknown) + return fmt.Errorf("record unmarshaling failed: %w", entities.ErrMetricUnknown) } return nil } - -func unmarshalFailed(reason error) error { - return fmt.Errorf("record unmarshaling failed: %w", reason) -} diff --git a/internal/storage/record_test.go b/internal/storage/record_test.go index bd1341f..3995f42 100644 --- a/internal/storage/record_test.go +++ b/internal/storage/record_test.go @@ -1,9 +1,13 @@ package storage import ( + "encoding/json" + "strconv" "testing" + "github.com/ex0rcist/metflix/internal/entities" "github.com/ex0rcist/metflix/internal/metrics" + "github.com/stretchr/testify/require" ) func TestCalculateRecordID(t *testing.T) { @@ -49,3 +53,54 @@ func TestRecord_CalculateRecordID(t *testing.T) { }) } } + +func TestRecordMarshalingNormalData(t *testing.T) { + tests := []struct { + name string + source Record + }{ + {name: "Should convert counter", source: Record{Name: "PollCount", Value: metrics.Counter(10)}}, + {name: "Should convert gauge", source: Record{Name: "Alloc", Value: metrics.Gauge(42.0)}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + json, err := tt.source.MarshalJSON() + if err != nil { + t.Fatalf("expected no error marshaling json, got: %v", err) + } + + target := new(Record) + err = target.UnmarshalJSON(json) + if err != nil { + t.Fatalf("expected no error unmarshaling json, got: %v", err) + } + + if tt.source != *target { + t.Fatal("expected records to be equal:", tt.source, target) + } + }) + } +} + +func TestRecordUnmarshalingCorruptedData(t *testing.T) { + tests := []struct { + name string + data string + expected error + }{ + {name: "Should fail on broken json", data: `{"name": "xxx",`, expected: &json.SyntaxError{}}, + {name: "Should fail on invalid counter", data: `{"name": "xxx", "kind": "counter", "value": "12.345"}`, expected: strconv.ErrSyntax}, + {name: "Should fail on invalid gauge", data: `{"name": "xxx", "kind": "gauge", "value": "12.)"}`, expected: strconv.ErrSyntax}, + {name: "Should fail on unknown kind", data: `{"name": "xxx", "kind": "unknown", "value": "12"}`, expected: entities.ErrMetricUnknown}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := new(Record) + + err := r.UnmarshalJSON([]byte(tt.data)) + require.ErrorAs(t, err, &tt.expected) + }) + } +} diff --git a/internal/utils/utils_test.go b/internal/utils/utils_test.go index a8f1dc3..3eacc73 100644 --- a/internal/utils/utils_test.go +++ b/internal/utils/utils_test.go @@ -4,8 +4,29 @@ import ( "net/http" "regexp" "testing" + "time" ) +func TestIntToDuration(t *testing.T) { + tests := []struct { + input int + expected time.Duration + }{ + {input: 0, expected: 0 * time.Second}, + {input: 1, expected: 1 * time.Second}, + {input: 60, expected: 60 * time.Second}, + {input: -1, expected: -1 * time.Second}, + {input: 3600, expected: 3600 * time.Second}, + } + + for _, tt := range tests { + result := IntToDuration(tt.input) + if result != tt.expected { + t.Errorf("IntToDuration(%d) = %v; expected %v", tt.input, result, tt.expected) + } + } +} + func TestHeadersToStr(t *testing.T) { tests := []struct { name string