package jsonfile import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "os" "reflect" "testing" "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" "git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/errors" "git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/tarball" ) type roundTripperFunc func(*http.Request) (*http.Response, error) func (f roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) { return f(req) } func newTestClient(statusCode int, body []byte) *http.Client { return &http.Client{ Transport: roundTripperFunc(func(req *http.Request) (*http.Response, error) { return &http.Response{ StatusCode: statusCode, Body: io.NopCloser(bytes.NewReader(body)), Header: make(http.Header), ContentLength: int64(len(body)), Request: req, }, nil }), } } func TestJSONFile_Field(t *testing.T) { type ( invalidStruct struct { name string number int } evidence struct { MaxAgeDuration string `json:"max_age_duration"` MaxAgeNumBlocks string `json:"max_age_num_blocks"` MaxBytes int64 `json:"max_bytes"` } ) tests := []struct { name string filepath string key string want interface{} err error }{ { name: "get string parameter", filepath: "testdata/jsonfile.json", key: "consensus_params.block.max_bytes", want: "22020096", }, { name: "get boolean parameter", filepath: "testdata/jsonfile.json", key: "launched", want: true, }, { name: "get array parameter", filepath: "testdata/jsonfile.json", key: "consensus_params.block.best_blocks", want: []int{100, 20, 11, 4, 2}, }, { name: "get number parameter", filepath: "testdata/jsonfile.json", key: "consensus_params.block.time_iota_ms", want: 1000, }, { name: "get coins parameter", filepath: "testdata/jsonfile.json", key: "app_state.bank.balances.[0].coins", want: sdk.Coins{sdk.NewCoin("stake", math.NewInt(95000000))}, }, { name: "get custom parameter", filepath: "testdata/jsonfile.json", key: "consensus_params.evidence", want: evidence{ MaxAgeDuration: "172800000000000", MaxAgeNumBlocks: "100000", MaxBytes: 1048576, }, }, { name: "invalid coins parameter", filepath: "testdata/jsonfile.json", key: "app_state.bank.balances.[0].coins", want: invalidStruct{name: "invalid", number: 110}, err: ErrInvalidValueType, }, { name: "invalid path", filepath: "testdata/jsonfile.json", key: "invalid.field.path", want: invalidStruct{name: "invalid", number: 110}, err: ErrFieldNotFound, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { f, err := FromPath(tt.filepath) require.NoError(t, err) t.Cleanup(func() { err = f.Close() require.NoError(t, err) }) out := reflect.New(reflect.TypeOf(tt.want)) err = f.Field(tt.key, out.Interface()) if tt.err != nil { require.Error(t, err) require.ErrorIs(t, err, tt.err) return } require.NoError(t, err) require.Equal(t, tt.want, out.Elem().Interface()) }) } } func TestJSONFile_Update(t *testing.T) { coins := sdk.NewCoin("bar", math.NewInt(500)) jsonCoins, err := json.Marshal(coins) require.NoError(t, err) tests := []struct { name string filepath string opts []UpdateFileOption want []interface{} err error }{ { name: "update string field", filepath: "testdata/jsonfile.json", opts: []UpdateFileOption{ WithKeyValue( "consensus_params.block.max_bytes", "22020096", ), }, want: []interface{}{float64(22020096)}, }, { name: "update string field to number", filepath: "testdata/jsonfile.json", opts: []UpdateFileOption{ WithKeyValueInt( "consensus_params.block.max_bytes", 22020096, ), }, want: []interface{}{float64(22020096)}, }, { name: "update number field", filepath: "testdata/jsonfile.json", opts: []UpdateFileOption{ WithKeyValueInt( "consensus_params.block.time_iota_ms", 1000, ), }, want: []interface{}{float64(1000)}, }, { name: "update timestamp field", filepath: "testdata/jsonfile.json", opts: []UpdateFileOption{ WithKeyValueTimestamp( "genesis_time", 10000000, ), }, want: nil, // TODO find a way to test timestamp values }, { name: "update two values type", filepath: "testdata/jsonfile.json", opts: []UpdateFileOption{ WithKeyValue( "consensus_params.block.max_bytes", "3000000", ), WithKeyValueInt( "consensus_params.block.time_iota_ms", 1000, ), }, want: []interface{}{float64(3000000), float64(1000)}, }, { name: "update coin field", filepath: "testdata/jsonfile.json", opts: []UpdateFileOption{ WithKeyValueByte( "app_state.crisis.params.constant_fee", jsonCoins, ), }, want: []interface{}{map[string]interface{}{ "denom": coins.Denom, "amount": coins.Amount.String(), }}, }, { name: "add non-existing field", filepath: "testdata/jsonfile.json", opts: []UpdateFileOption{ WithKeyValue( "app_state.auth.params.sig_verify_cost_ed25519", "111", ), }, want: []interface{}{float64(111)}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { f, err := FromPath(tt.filepath) require.NoError(t, err) // Rollback files after change b, err := f.Bytes() require.NoError(t, err) t.Cleanup(func() { var prettyJSON bytes.Buffer err := json.Indent(&prettyJSON, b, "", " ") require.NoError(t, err) err = truncate(f.file, 0) require.NoError(t, err) err = f.Reset() require.NoError(t, err) _, err = f.file.Write(prettyJSON.Bytes()) require.NoError(t, err) err = f.Close() require.NoError(t, err) }) err = f.Update(tt.opts...) if tt.err != nil { require.Error(t, err) require.ErrorIs(t, err, tt.err) return } require.NoError(t, err) updates := make(map[string][]byte) for _, opt := range tt.opts { opt(updates) } if tt.want != nil { got := make([]interface{}, 0) for key := range updates { var newValue interface{} err := f.Field(key, &newValue) require.NoError(t, err) got = append(got, newValue) } require.ElementsMatch(t, tt.want, got) } }) } } func TestJSONFile_Hash(t *testing.T) { tests := []struct { name string filepath string want string err error }{ { name: "file hash", filepath: "testdata/jsonfile.json", want: "036dbc0020f4ab5604f46a8e5a05c368e4cba41f48fcac2864641902c1dfcad5", }, { name: "not found file", filepath: "testdata/genesis_not_found.json", err: errors.New( "cannot open the file: open testdata/genesis_not_found.json: no such file or directory", ), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { f, err := FromPath(tt.filepath) if tt.err != nil { require.Error(t, err) require.Equal(t, err.Error(), tt.err.Error()) return } require.NoError(t, err) t.Cleanup(func() { err = f.Close() require.NoError(t, err) }) got, err := f.Hash() if tt.err != nil { require.Error(t, err) require.ErrorIs(t, err, tt.err) return } require.NoError(t, err) require.Equal(t, tt.want, got) }) } } func TestFromURL(t *testing.T) { type args struct { url string filepath string tarballFileName string } tests := []struct { name string args args verifyField string wantField string err error }{ { name: "JSON URL", args: args{ filepath: "testdata/jsonfile.json", }, verifyField: "chain_id", wantField: "earth-1", }, { name: "tarball URL", args: args{ filepath: "testdata/example.tar.gz", tarballFileName: "example.json", }, verifyField: "chain_id", wantField: "gaia-1", }, { name: "invalid tarball file name", args: args{ filepath: "testdata/example.tar.gz", tarballFileName: "invalid.json", }, err: tarball.ErrGzipFileNotFound, }, { name: "invalid link", args: args{ url: "https://google.com/invalid_example.json", }, err: ErrInvalidURL, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { url := tt.args.url if url == "" { url = "https://example.com/testdata" } var body []byte if tt.args.filepath != "" { var err error body, err = os.ReadFile(tt.args.filepath) require.NoError(t, err) } statusCode := http.StatusOK if tt.err == ErrInvalidURL { statusCode = http.StatusNotFound } client := newTestClient(statusCode, body) filepath := fmt.Sprintf("%s/jsonfile.json", t.TempDir()) got, err := FromURLWithClient(context.TODO(), url, filepath, tt.args.tarballFileName, client) if tt.err != nil { require.Error(t, err) require.ErrorIs(t, err, tt.err) return } require.NoError(t, err) t.Cleanup(func() { err = got.Close() require.NoError(t, err) }) var verificationField string err = got.Field(tt.verifyField, &verificationField) require.NoError(t, err) require.Equal(t, tt.wantField, verificationField) }) } }