2021-06-18 09:24:16 +03:00
package metadata
import (
2021-07-06 12:36:32 +03:00
"context"
2021-06-18 09:24:16 +03:00
"fmt"
"io/ioutil"
2021-07-06 12:36:32 +03:00
"net/http"
"path/filepath"
"strings"
2021-06-18 09:24:16 +03:00
"testing"
2022-10-28 09:46:52 +03:00
"github.com/hasura/graphql-engine/cli/v2/internal/errors"
2021-07-06 12:36:32 +03:00
"github.com/hasura/graphql-engine/cli/v2/internal/hasura"
"github.com/hasura/graphql-engine/cli/v2/pkg/migrate"
2022-10-28 09:46:52 +03:00
"github.com/stretchr/testify/assert"
2021-06-18 09:24:16 +03:00
"github.com/stretchr/testify/require"
"github.com/hasura/graphql-engine/cli/v2/internal/testutil"
)
func TestProjectMetadataOps_Apply ( t * testing . T ) {
port , teardown := testutil . StartHasura ( t , testutil . HasuraDockerImage )
hgeEndpoint := fmt . Sprintf ( "http://localhost:%s" , port )
defer teardown ( )
type fields struct {
projectDirectory string
endpointString string
}
2022-10-28 09:46:52 +03:00
// Named for reuse below. We only care about the inconsistent_objects array
// as a set, i.e. ordering may change:
2022-06-08 18:31:28 +03:00
v3Expected :=
` { "is_consistent" : false , "inconsistent_objects" : [
{ "definition" : { "name" : "t4" , "schema" : "pub" } , "name" : "table pub.t4 in source default" , "reason" : "Inconsistent object: no such table/view exists in source: \"pub.t4\"" , "type" : "table" } ,
{ "definition" : { "name" : "t3" , "schema" : "pub" } , "name" : "table pub.t3 in source default" , "reason" : "Inconsistent object: no such table/view exists in source: \"pub.t3\"" , "type" : "table" } ,
2023-06-06 16:27:46 +03:00
{ "definition" : { "name" : "t1" , "schema" : "public" } , "name" : "table t1 in source default" , "reason" : "Inconsistent object: no such table/view exists in source: \"t1\"" , "type" : "table" } ,
{ "definition" : { "name" : "t2" , "schema" : "public" } , "name" : "table t2 in source default" , "reason" : "Inconsistent object: no such table/view exists in source: \"t2\"" , "type" : "table" }
2022-06-08 18:31:28 +03:00
] } `
2021-06-18 09:24:16 +03:00
tests := [ ] struct {
2022-10-28 09:46:52 +03:00
name string
fields fields
want string
wantErr bool
assertErr require . ErrorAssertionFunc
2021-06-18 09:24:16 +03:00
} {
{
"can apply metadata from config v3 project" ,
fields {
projectDirectory : "testdata/projectv3" ,
endpointString : hgeEndpoint ,
} ,
2022-06-08 18:31:28 +03:00
v3Expected ,
2021-06-18 09:24:16 +03:00
false ,
2022-10-28 09:46:52 +03:00
require . NoError ,
2021-06-18 09:24:16 +03:00
} ,
2021-09-29 14:11:24 +03:00
{
"can apply metadata from config v3 project in file mode (json)" ,
fields {
projectDirectory : "testdata/projectv3-file-mode-json" ,
endpointString : hgeEndpoint ,
} ,
2022-06-08 18:31:28 +03:00
v3Expected ,
2021-09-29 14:11:24 +03:00
false ,
2022-10-28 09:46:52 +03:00
require . NoError ,
2021-09-29 14:11:24 +03:00
} ,
{
"can apply metadata from config v3 project in file mode (yaml)" ,
fields {
projectDirectory : "testdata/projectv3-file-mode-yaml" ,
endpointString : hgeEndpoint ,
} ,
2022-06-08 18:31:28 +03:00
v3Expected ,
2021-09-29 14:11:24 +03:00
false ,
2022-10-28 09:46:52 +03:00
require . NoError ,
2021-09-29 14:11:24 +03:00
} ,
2021-06-18 09:24:16 +03:00
{
"can apply metadata from config v2 project" ,
fields {
projectDirectory : "testdata/projectv2" ,
endpointString : hgeEndpoint ,
} ,
"" ,
true ,
2022-10-28 09:46:52 +03:00
require . ErrorAssertionFunc ( func ( tt require . TestingT , err error , i ... interface { } ) {
require . IsType ( t , & errors . Error { } , err )
e := err . ( * errors . Error )
require . Equal ( t , errors . Op ( "metadata.ProjectMetadata.Apply" ) , e . Op )
} ) ,
2021-06-18 09:24:16 +03:00
} ,
2021-09-29 14:11:24 +03:00
{
"can apply metadata from config v2 project file mode (json)" ,
fields {
projectDirectory : "testdata/projectv2-file-mode-json" ,
endpointString : hgeEndpoint ,
} ,
"" ,
true ,
2022-10-28 09:46:52 +03:00
require . ErrorAssertionFunc ( func ( tt require . TestingT , err error , i ... interface { } ) {
require . IsType ( t , & errors . Error { } , err )
e := err . ( * errors . Error )
require . Equal ( t , errors . Op ( "metadata.ProjectMetadata.Apply" ) , e . Op )
} ) ,
2021-09-29 14:11:24 +03:00
} ,
{
"can apply metadata from config v2 project file mode (yaml)" ,
fields {
projectDirectory : "testdata/projectv2-file-mode-yaml" ,
endpointString : hgeEndpoint ,
} ,
"" ,
true ,
2022-10-28 09:46:52 +03:00
require . ErrorAssertionFunc ( func ( tt require . TestingT , err error , i ... interface { } ) {
require . IsType ( t , & errors . Error { } , err )
e := err . ( * errors . Error )
require . Equal ( t , errors . Op ( "metadata.ProjectMetadata.Apply" ) , e . Op )
} ) ,
2021-09-29 14:11:24 +03:00
} ,
2021-06-18 09:24:16 +03:00
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
p , err := NewProjectMetadata ( tt . fields . projectDirectory , WithAdminSecret ( testutil . TestAdminSecret ) , WithEndpoint ( tt . fields . endpointString ) )
require . NoError ( t , err )
got , err := p . Apply ( )
2022-10-28 09:46:52 +03:00
tt . assertErr ( t , err )
2021-06-18 09:24:16 +03:00
if tt . wantErr {
2022-10-28 09:46:52 +03:00
return
2021-06-18 09:24:16 +03:00
}
2022-10-28 09:46:52 +03:00
require . NotNil ( t , got )
gotb , err := ioutil . ReadAll ( got )
require . NoError ( t , err )
require . JSONEq ( t , tt . want , string ( gotb ) )
2021-06-18 09:24:16 +03:00
} )
}
}
func TestProjectMetadataOps_Parse ( t * testing . T ) {
port , teardown := testutil . StartHasura ( t , testutil . HasuraDockerImage )
hgeEndpoint := fmt . Sprintf ( "http://localhost:%s" , port )
defer teardown ( )
type fields struct {
projectDirectory string
}
tests := [ ] struct {
name string
fields fields
wantGolden string
wantErr bool
2022-10-28 09:46:52 +03:00
assertErr require . ErrorAssertionFunc
2021-06-18 09:24:16 +03:00
} {
{
"can generate json metadata from config v3 project" ,
fields {
projectDirectory : "testdata/projectv3" ,
} ,
2022-03-10 11:12:55 +03:00
"testdata/metadata_parse_test/config-v3.project.want.generated.golden.json" ,
2021-06-18 09:24:16 +03:00
false ,
2022-10-28 09:46:52 +03:00
require . NoError ,
2021-06-18 09:24:16 +03:00
} ,
2021-09-29 14:11:24 +03:00
{
"can generate json metadata from config v3 project in filemode (json)" ,
fields {
projectDirectory : "testdata/projectv3-file-mode-json" ,
} ,
"testdata/metadata_parse_test/config-v3.golden.json" ,
false ,
2022-10-28 09:46:52 +03:00
require . NoError ,
2021-09-29 14:11:24 +03:00
} ,
{
"can generate json metadata from config v3 project in filemode (yaml)" ,
fields {
projectDirectory : "testdata/projectv3-file-mode-yaml" ,
} ,
"testdata/metadata_parse_test/config-v3.golden.json" ,
false ,
2022-10-28 09:46:52 +03:00
require . NoError ,
2021-09-29 14:11:24 +03:00
} ,
2021-06-18 09:24:16 +03:00
{
"can generate json metadata from config v2 project" ,
fields {
projectDirectory : "testdata/projectv2" ,
} ,
2022-03-10 11:12:55 +03:00
"testdata/metadata_parse_test/config-v2.project.want.generated.golden.json" ,
2021-06-18 09:24:16 +03:00
false ,
2022-10-28 09:46:52 +03:00
require . NoError ,
2021-06-18 09:24:16 +03:00
} ,
2021-09-29 14:11:24 +03:00
{
"can generate json metadata from config v2 project in filemode (json)" ,
fields {
projectDirectory : "testdata/projectv2-file-mode-json" ,
} ,
"testdata/metadata_parse_test/config-v2.golden.json" ,
false ,
2022-10-28 09:46:52 +03:00
require . NoError ,
2021-09-29 14:11:24 +03:00
} ,
{
"can generate json metadata from config v2 project in filemode (yaml)" ,
fields {
projectDirectory : "testdata/projectv2-file-mode-yaml" ,
} ,
"testdata/metadata_parse_test/config-v2.golden.json" ,
false ,
2022-10-28 09:46:52 +03:00
require . NoError ,
2021-09-29 14:11:24 +03:00
} ,
2021-06-18 09:24:16 +03:00
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
p , err := NewProjectMetadata ( tt . fields . projectDirectory , WithEndpoint ( hgeEndpoint ) , WithAdminSecret ( testutil . TestAdminSecret ) )
2022-10-28 09:46:52 +03:00
assert . NoError ( t , err )
2021-06-18 09:24:16 +03:00
got , err := p . Parse ( )
2022-10-28 09:46:52 +03:00
tt . assertErr ( t , err )
2021-06-18 09:24:16 +03:00
if tt . wantErr {
2022-10-28 09:46:52 +03:00
return
2021-06-18 09:24:16 +03:00
}
2022-10-28 09:46:52 +03:00
require . NotNil ( t , got )
gotb , err := ioutil . ReadAll ( got )
require . NoError ( t , err )
// uncomment to update golden file
//assert.NoError(t, ioutil.WriteFile(tt.wantGolden, gotb, os.ModePerm))
wantb , err := ioutil . ReadFile ( tt . wantGolden )
require . NoError ( t , err )
require . JSONEq ( t , string ( wantb ) , string ( gotb ) )
2021-06-18 09:24:16 +03:00
} )
}
}
func TestProjectMetadataOps_Diff ( t * testing . T ) {
port , teardown := testutil . StartHasura ( t , testutil . HasuraDockerImage )
hgeEndpoint := fmt . Sprintf ( "http://localhost:%s" , port )
defer teardown ( )
type fields struct {
projectDirectory string
}
tests := [ ] struct {
name string
fields fields
wantGolden string
wantErr bool
2022-10-28 09:46:52 +03:00
assertErr require . ErrorAssertionFunc
2021-06-18 09:24:16 +03:00
} {
{
"can generate diff on config v3 project" ,
fields {
projectDirectory : "testdata/projectv3" ,
} ,
"testdata/metadata_diff_test/config_v3_diff" ,
false ,
2022-10-28 09:46:52 +03:00
require . NoError ,
2021-06-18 09:24:16 +03:00
} ,
{
"can generate diff on config v2 project" ,
fields {
projectDirectory : "testdata/projectv2" ,
} ,
"testdata/metadata_diff_test/config_v2_diff" ,
false ,
2022-10-28 09:46:52 +03:00
require . NoError ,
2021-06-18 09:24:16 +03:00
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
p , err := NewProjectMetadata ( tt . fields . projectDirectory , WithEndpoint ( hgeEndpoint ) , WithAdminSecret ( testutil . TestAdminSecret ) )
require . NoError ( t , err )
got , err := p . Diff ( )
2022-10-28 09:46:52 +03:00
tt . assertErr ( t , err )
2021-06-18 09:24:16 +03:00
if tt . wantErr {
2022-10-28 09:46:52 +03:00
return
2021-06-18 09:24:16 +03:00
}
gotb , err := ioutil . ReadAll ( got )
require . NoError ( t , err )
2022-03-10 11:12:55 +03:00
// uncomment to update golden file
//require.NoError(t, ioutil.WriteFile(tt.wantGolden, gotb, os.ModePerm))
2021-06-18 09:24:16 +03:00
wantb , err := ioutil . ReadFile ( tt . wantGolden )
require . NoError ( t , err )
require . Equal ( t , string ( wantb ) , string ( gotb ) )
} )
}
}
func TestProjectMetadata_Reload ( t * testing . T ) {
port , teardown := testutil . StartHasura ( t , testutil . HasuraDockerImage )
hgeEndpoint := fmt . Sprintf ( "http://localhost:%s" , port )
defer teardown ( )
type fields struct {
projectDirectory string
endpointString string
}
tests := [ ] struct {
name string
fields fields
want string
2021-12-15 20:54:47 +03:00
wantErr require . ErrorAssertionFunc
2021-06-18 09:24:16 +03:00
} {
{
"can reload metadata" ,
fields {
projectDirectory : "testdata/projectv3" ,
endpointString : hgeEndpoint ,
} ,
2022-03-03 16:33:43 +03:00
` {
"is_consistent" : true ,
"message" : "success"
} ` ,
2021-12-15 20:54:47 +03:00
require . NoError ,
2021-06-18 09:24:16 +03:00
} ,
2021-12-15 20:54:47 +03:00
// TODO: automate the following tests
// following tests are currently ran manually by making use of https://github.com/Shopify/toxiproxy
//{
// "can return expected error type when graphql engine connections fail",
// fields{
// projectDirectory: "testdata/projectv3",
// endpointString: "http://localhost:12345/something",
// },
// `{"message": "success"}`,
// func(t require.TestingT, err error, i ...interface{}) {
// var e *url.Error
// require.Truef(t, errors.As(err, &e), "expected err to be an instance of %v but got %v", reflect.TypeOf(&url.Error{}), reflect.TypeOf(err))
// },
//},
// config.json
// [
// {
// "name": "hasura",
// "listen": "[::]:18080",
// "upstream": "localhost:8080",
// "enabled": true
// }
// ]
//
// 1. Simulate reset peer
// $ toxiproxy-cli toxic add -t reset_peer hasura
// 2. Simulate timeout
// $ toxiproxy-cli toxic add -t timeout -a timeout=1 hasura
//{
// "return expect error type when graphql engine connections reset",
// fields{
// projectDirectory: "testdata/projectv3",
// endpointString: "http://localhost:18080",
// },
// `{"message": "success"}`,
// func(t require.TestingT, err error, i ...interface{}) {
// var e *url.Error
// require.Truef(t, errors.As(err, &e), "expected err to be an instance of %v but got %v", reflect.TypeOf(&url.Error{}), reflect.TypeOf(err))
// },
//},
2021-06-18 09:24:16 +03:00
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
2021-12-15 20:54:47 +03:00
p , err := NewProjectMetadata ( tt . fields . projectDirectory , WithEndpoint ( tt . fields . endpointString ) , WithAdminSecret ( testutil . TestAdminSecret ) )
tt . wantErr ( t , err )
if p != nil {
got , err := p . Reload ( )
tt . wantErr ( t , err )
gotb , err := ioutil . ReadAll ( got )
require . NoError ( t , err )
require . JSONEq ( t , tt . want , string ( gotb ) )
2021-06-18 09:24:16 +03:00
}
} )
}
}
2021-07-06 12:36:32 +03:00
func TestProjectMetadata_GetInconsistentMetadata ( t * testing . T ) {
type before func ( t * testing . T , p * ProjectMetadata , m * migrate . ProjectMigrate , hgePort , queryEndpoint string )
configV2Before := func ( t * testing . T , metadata * ProjectMetadata , migrations * migrate . ProjectMigrate , hgePort , queryEndpoint string ) {
// - apply all migrations
// - apply metadata
// - drop a table via run_sql API
// - reload metadata
2021-08-16 09:43:11 +03:00
_ , err := migrations . Apply ( migrate . ApplyOnAllDatabases ( ) )
2021-07-06 12:36:32 +03:00
require . NoError ( t , err )
_ , err = metadata . Apply ( )
require . NoError ( t , err )
// remove a table from database
c := testutil . NewHttpcClient ( t , hgePort , nil )
r , err := c . NewRequest (
http . MethodPost ,
queryEndpoint ,
hasura . RequestBody {
Type : "run_sql" ,
Args : hasura . PGRunSQLInput {
SQL : "DROP table t1;" ,
CheckMetadataConsistency : func ( ) * bool { var v = false ; return & v } ( ) ,
} ,
} ,
)
require . NoError ( t , err )
resp , err := c . Do ( context . Background ( ) , r , nil )
require . Equal ( t , http . StatusOK , resp . StatusCode )
require . NoError ( t , err )
_ , err = metadata . Reload ( )
require . NoError ( t , err )
}
type fields struct {
projectDirectory string
}
tests := [ ] struct {
name string
fields fields
before before
hasuraImage string
queryEndpoint string
wantErr bool
2022-10-28 09:46:52 +03:00
assertErr require . ErrorAssertionFunc
2021-07-06 12:36:32 +03:00
} {
{
"can list inconsistent metadata config v3" ,
fields {
projectDirectory : "testdata/projectv3" ,
} ,
func ( t * testing . T , metadata * ProjectMetadata , _ * migrate . ProjectMigrate , _ string , _ string ) {
_ , err := metadata . Apply ( )
require . NoError ( t , err )
} ,
testutil . HasuraDockerImage ,
"v2/query" ,
false ,
2022-10-28 09:46:52 +03:00
require . NoError ,
2021-07-06 12:36:32 +03:00
} ,
{
"can list inconsistent metadata config v2" ,
fields {
projectDirectory : "testdata/projectv2" ,
} ,
configV2Before ,
testutil . HasuraDockerImage ,
"v2/query" ,
false ,
2022-10-28 09:46:52 +03:00
require . NoError ,
2021-07-06 12:36:32 +03:00
} ,
{
"can list inconsistent metadata config v2 v1.3.3" ,
fields {
projectDirectory : "testdata/projectv2" ,
} ,
configV2Before ,
"hasura/graphql-engine:v1.3.3" ,
"v1/query" ,
false ,
2022-10-28 09:46:52 +03:00
require . NoError ,
2021-07-06 12:36:32 +03:00
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
port , teardown := testutil . StartHasura ( t , tt . hasuraImage )
hgeEndpoint := fmt . Sprintf ( "%s:%s" , testutil . BaseURL , port )
defer teardown ( )
migrations , err := migrate . NewProjectMigrate ( tt . fields . projectDirectory , migrate . WithEndpoint ( hgeEndpoint ) , migrate . WithAdminSecret ( testutil . TestAdminSecret ) )
require . NoError ( t , err )
metadata , err := NewProjectMetadata ( tt . fields . projectDirectory , WithEndpoint ( hgeEndpoint ) , WithAdminSecret ( testutil . TestAdminSecret ) )
require . NoError ( t , err )
if tt . before != nil {
tt . before ( t , metadata , migrations , port , tt . queryEndpoint )
}
got , err := metadata . GetInconsistentMetadata ( )
2022-10-28 09:46:52 +03:00
tt . assertErr ( t , err )
2021-07-06 12:36:32 +03:00
if tt . wantErr {
2022-10-28 09:46:52 +03:00
return
2021-07-06 12:36:32 +03:00
}
gotb , err := ioutil . ReadAll ( got )
require . NoError ( t , err )
goldenFile := filepath . Join ( "testdata/get_inconsistent_metadata_test" , strings . Join ( strings . Split ( tt . name , " " ) , "_" ) + ".golden.json" )
// uncomment the following line to update test golden file
// require.NoError(t, ioutil.WriteFile(goldenFile, gotb, 0655))
wantb , err := ioutil . ReadFile ( goldenFile )
require . NoError ( t , err )
require . JSONEq ( t , string ( wantb ) , string ( gotb ) )
} )
}
}
2023-10-25 23:54:37 +03:00
func TestProjectMetadata_Export ( t * testing . T ) {
port , teardown := testutil . StartHasura ( t , testutil . HasuraDockerImage )
hgeEndpoint := fmt . Sprintf ( "http://localhost:%s" , port )
defer teardown ( )
type fields struct {
projectDirectory string
}
tests := [ ] struct {
name string
fields fields
want string
wantErr bool
assertErr require . ErrorAssertionFunc
} {
{
"can export metadata from config" ,
fields {
projectDirectory : "testdata/projectv2" ,
} ,
"testdata/metadata_export_test/config-v2.json" ,
false ,
require . NoError ,
} ,
{
"can export json metadata from config in filemode (json)" ,
fields {
projectDirectory : "testdata/projectv2-file-mode-json" ,
} ,
"testdata/metadata_export_test/config-v2.json" ,
false ,
require . NoError ,
} ,
{
"can export yaml metadata from config in filemode (yaml)" ,
fields {
projectDirectory : "testdata/projectv2-file-mode-yaml" ,
} ,
"testdata/metadata_export_test/config-v2.yaml" ,
false ,
require . NoError ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
p , err := NewProjectMetadata ( tt . fields . projectDirectory , WithEndpoint ( hgeEndpoint ) , WithAdminSecret ( testutil . TestAdminSecret ) )
require . NoError ( t , err )
got , err := p . Export ( )
tt . assertErr ( t , err )
if tt . wantErr {
return
}
gotb , err := ioutil . ReadAll ( got )
require . NoError ( t , err )
// uncomment to update golden file
//require.NoError(t, ioutil.WriteFile(tt.wantGolden, gotb, os.ModePerm))
wantb , err := ioutil . ReadFile ( tt . want )
require . NoError ( t , err )
require . Equal ( t , string ( wantb ) , string ( gotb ) )
} )
}
}