/* Copyright © 2022-2025 Thomas von Dein This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package lib import ( "fmt" "io" "strings" "testing" "github.com/stretchr/testify/assert" "codeberg.org/scip/tablizer/cfg" ) var input = []struct { name string text string separator string }{ { name: "tabular-data", separator: cfg.SeparatorTemplates[":default:"], text: ` ONE TWO THREE asd igig cxxxncnc 19191 EDD 1 X`, }, { name: "csv-data", separator: ",", text: ` ONE,TWO,THREE asd,igig,cxxxncnc 19191,"EDD 1",X`, }, } func TestParser(t *testing.T) { data := Tabdata{ maxwidthHeader: 5, columns: 3, headers: []string{ "ONE", "TWO", "THREE", }, entries: [][]string{ {"asd", "igig", "cxxxncnc"}, {"19191", "EDD 1", "X"}, }, } for _, testdata := range input { testname := fmt.Sprintf("parse-%s", testdata.name) t.Run(testname, func(t *testing.T) { readFd := strings.NewReader(strings.TrimSpace(testdata.text)) conf := cfg.Config{Separator: testdata.separator} gotdata, err := wrapValidateParser(conf, readFd) assert.NoError(t, err) assert.EqualValues(t, data, gotdata) }) } } func TestParserPatternmatching(t *testing.T) { var tests = []struct { name string entries [][]string patterns []*cfg.Pattern invert bool wanterror bool }{ { name: "match", entries: [][]string{ {"asd", "igig", "cxxxncnc"}, }, patterns: []*cfg.Pattern{{Pattern: "ig"}}, invert: false, }, { name: "invert", entries: [][]string{ {"19191", "EDD 1", "X"}, }, patterns: []*cfg.Pattern{{Pattern: "ig"}}, invert: true, }, } for _, inputdata := range input { for _, testdata := range tests { testname := fmt.Sprintf("parse-%s-with-pattern-%s-inverted-%t", inputdata.name, testdata.name, testdata.invert) t.Run(testname, func(t *testing.T) { conf := cfg.Config{ InvertMatch: testdata.invert, Patterns: testdata.patterns, Separator: inputdata.separator, } _ = conf.PreparePattern(testdata.patterns) readFd := strings.NewReader(strings.TrimSpace(inputdata.text)) data, err := wrapValidateParser(conf, readFd) if testdata.wanterror { assert.Error(t, err) } else { assert.NoError(t, err) assert.EqualValues(t, testdata.entries, data.entries) } }) } } } func TestParserIncompleteRows(t *testing.T) { data := Tabdata{ maxwidthHeader: 5, columns: 3, headers: []string{ "ONE", "TWO", "THREE", }, entries: [][]string{ {"asd", "igig", ""}, {"19191", "EDD 1", "X"}, }, } table := ` ONE TWO THREE asd igig 19191 EDD 1 X` readFd := strings.NewReader(strings.TrimSpace(table)) conf := cfg.Config{Separator: cfg.SeparatorTemplates[":default:"]} gotdata, err := wrapValidateParser(conf, readFd) assert.NoError(t, err) assert.EqualValues(t, data, gotdata) } func TestParserJSONInput(t *testing.T) { var tests = []struct { name string input string expect Tabdata wanterror bool // true: expect fail, false: expect success }{ { // too deep nesting name: "invalidjson", wanterror: true, input: `[ { "item": { "NAME": "postgres-operator-7f4c7c8485-ntlns", "READY": "1/1", "STATUS": "Running", "RESTARTS": "0", "AGE": "24h" } } `, expect: Tabdata{}, }, { // contains nil, int and float values name: "niljson", wanterror: false, input: `[ { "NAME": "postgres-operator-7f4c7c8485-ntlns", "READY": "1/1", "STATUS": "Running", "RESTARTS": 0, "AGE": null, "X": 12, "Y": 34.222 } ]`, expect: Tabdata{ columns: 7, headers: []string{"NAME", "READY", "STATUS", "RESTARTS", "AGE", "X", "Y"}, entries: [][]string{ []string{ "postgres-operator-7f4c7c8485-ntlns", "1/1", "Running", "0", "", "12", "34.222000", }, }, }, }, { // one field missing + different order // but shall not fail name: "kgpfail", wanterror: false, input: `[ { "NAME": "postgres-operator-7f4c7c8485-ntlns", "READY": "1/1", "STATUS": "Running", "RESTARTS": "0", "AGE": "24h" }, { "NAME": "wal-g-exporter-778dcd95f5-wcjzn", "RESTARTS": "0", "READY": "1/1", "AGE": "24h" } ]`, expect: Tabdata{ columns: 5, headers: []string{"NAME", "READY", "STATUS", "RESTARTS", "AGE"}, entries: [][]string{ []string{ "postgres-operator-7f4c7c8485-ntlns", "1/1", "Running", "0", "24h", }, []string{ "wal-g-exporter-778dcd95f5-wcjzn", "1/1", "", "0", "24h", }, }, }, }, { name: "kgp", wanterror: false, input: `[ { "NAME": "postgres-operator-7f4c7c8485-ntlns", "READY": "1/1", "STATUS": "Running", "RESTARTS": "0", "AGE": "24h" }, { "NAME": "wal-g-exporter-778dcd95f5-wcjzn", "STATUS": "Running", "READY": "1/1", "RESTARTS": "0", "AGE": "24h" } ]`, expect: Tabdata{ columns: 5, headers: []string{"NAME", "READY", "STATUS", "RESTARTS", "AGE"}, entries: [][]string{ []string{ "postgres-operator-7f4c7c8485-ntlns", "1/1", "Running", "0", "24h", }, []string{ "wal-g-exporter-778dcd95f5-wcjzn", "1/1", "Running", "0", "24h", }, }, }, }, } for _, testdata := range tests { testname := fmt.Sprintf("parse-json-%s", testdata.name) t.Run(testname, func(t *testing.T) { conf := cfg.Config{InputJSON: true} readFd := strings.NewReader(strings.TrimSpace(testdata.input)) data, err := wrapValidateParser(conf, readFd) if testdata.wanterror { assert.Error(t, err) } else { assert.NoError(t, err) assert.EqualValues(t, testdata.expect, data) } }) } } func TestParserSeparators(t *testing.T) { list := []string{"alpha", "beta", "delta"} tests := []struct { input string sep string }{ { input: `🎲`, sep: ":nonprint:", }, { input: `|`, sep: ":pipe:", }, { input: ` `, sep: ":spaces:", }, { input: " \t ", sep: ":tab:", }, { input: `-`, sep: ":nonword:", }, { input: `//$`, sep: ":special:", }, } for _, testdata := range tests { testname := fmt.Sprintf("parse-%s", testdata.sep) t.Run(testname, func(t *testing.T) { header := strings.Join(list, testdata.input) row := header content := header + "\n" + row readFd := strings.NewReader(strings.TrimSpace(content)) conf := cfg.Config{Separator: testdata.sep} conf.ApplyDefaults() gotdata, err := wrapValidateParser(conf, readFd) assert.NoError(t, err) assert.EqualValues(t, [][]string{list}, gotdata.entries) }) } } func TestParserSetHeaders(t *testing.T) { row := []string{"c", "b", "c", "d", "e"} tests := []struct { name string custom []string expect []string auto bool }{ { name: "default", expect: row, }, { name: "auto", expect: strings.Split("1 2 3 4 5", " "), auto: true, }, { name: "custom-complete", custom: strings.Split("A B C D E", " "), expect: strings.Split("A B C D E", " "), }, { name: "custom-too-short", custom: strings.Split("A B", " "), expect: strings.Split("A B 3 4 5", " "), }, { name: "custom-too-long", custom: strings.Split("A B C D E F G", " "), expect: strings.Split("A B C D E", " "), }, } for _, testdata := range tests { testname := fmt.Sprintf("parse-%s", testdata.name) t.Run(testname, func(t *testing.T) { conf := cfg.Config{ AutoHeaders: testdata.auto, CustomHeaders: testdata.custom, } headers := SetHeaders(conf, row) assert.NotNil(t, headers) assert.EqualValues(t, testdata.expect, headers) }) } } func wrapValidateParser(conf cfg.Config, input io.Reader) (Tabdata, error) { data, err := Parse(conf, input) if err != nil { return data, err } err = ValidateConsistency(&data) return data, err }