github.com/juranki/gonetable@v0.0.0-20220909144800-647dacb32e0b/schema_test.go (about) 1 package gonetable_test 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "os" 7 "reflect" 8 "testing" 9 10 "github.com/aws/aws-sdk-go-v2/aws" 11 "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue" 12 "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" 13 "github.com/juranki/gonetable" 14 ) 15 16 func TestNewSchema(t *testing.T) { 17 type args struct { 18 docSamples []gonetable.Document 19 } 20 tests := []struct { 21 name string 22 args args 23 wantErr error 24 }{ 25 { 26 name: "invalid index", 27 args: args{ 28 docSamples: []gonetable.Document{&InvalidIndex{}}, 29 }, 30 wantErr: gonetable.ErrIndexName, 31 }, 32 { 33 name: "no doc samples", 34 args: args{ 35 docSamples: []gonetable.Document{}, 36 }, 37 wantErr: gonetable.ErrNoDocSamples, 38 }, 39 { 40 name: "duplicate type id", 41 args: args{ 42 docSamples: []gonetable.Document{&InvalidIndex{}, &InvalidIndex{}}, 43 }, 44 wantErr: gonetable.ErrDuplicateTypeID, 45 }, 46 { 47 name: "simple doc", 48 args: args{ 49 docSamples: []gonetable.Document{&MinimalDoc{}}, 50 }, 51 wantErr: nil, 52 }, 53 { 54 name: "one index", 55 args: args{ 56 docSamples: []gonetable.Document{&WithIndex{}}, 57 }, 58 wantErr: nil, 59 }, 60 } 61 for _, tt := range tests { 62 t.Run(tt.name, func(t *testing.T) { 63 _, err := gonetable.NewSchema(tt.args.docSamples) 64 if err == nil { 65 if tt.wantErr != nil { 66 t.Error("Expected error") 67 } 68 return 69 } 70 if tt.wantErr.Error() == err.Error() { 71 return 72 } 73 t.Errorf("error = '%v', want '%v'", err.Error(), tt.wantErr.Error()) 74 }) 75 } 76 } 77 78 func TestSchema_AttributeDefinitions(t *testing.T) { 79 tests := []struct { 80 name string 81 docSamples []gonetable.Document 82 want []types.AttributeDefinition 83 }{ 84 { 85 name: "minimal", 86 docSamples: []gonetable.Document{&MinimalDoc{}}, 87 want: []types.AttributeDefinition{ 88 { 89 AttributeName: aws.String("PK"), 90 AttributeType: types.ScalarAttributeTypeS, 91 }, 92 { 93 AttributeName: aws.String("SK"), 94 AttributeType: types.ScalarAttributeTypeS, 95 }, 96 }, 97 }, 98 { 99 name: "withIndex", 100 docSamples: []gonetable.Document{&WithIndex{}}, 101 want: []types.AttributeDefinition{ 102 { 103 AttributeName: aws.String("PK"), 104 AttributeType: types.ScalarAttributeTypeS, 105 }, 106 { 107 AttributeName: aws.String("SK"), 108 AttributeType: types.ScalarAttributeTypeS, 109 }, 110 { 111 AttributeName: aws.String("GSI1PK"), 112 AttributeType: types.ScalarAttributeTypeS, 113 }, 114 { 115 AttributeName: aws.String("GSI1SK"), 116 AttributeType: types.ScalarAttributeTypeS, 117 }, 118 }, 119 }, 120 } 121 for _, tt := range tests { 122 t.Run(tt.name, func(t *testing.T) { 123 s, err := gonetable.NewSchema(tt.docSamples) 124 if err != nil { 125 t.Fatal(err) 126 } 127 if got := s.AttributeDefinitions(); !reflect.DeepEqual(got, tt.want) { 128 json.NewEncoder(os.Stdout).Encode(got) 129 json.NewEncoder(os.Stdout).Encode(tt.want) 130 t.Errorf("Schema.AttributeDefinitions() = %v, want %v", got, tt.want) 131 } 132 }) 133 } 134 } 135 136 func TestSchema_KeySchema(t *testing.T) { 137 tests := []struct { 138 name string 139 docSamples []gonetable.Document 140 want []types.KeySchemaElement 141 }{ 142 { 143 name: "minimal", 144 docSamples: []gonetable.Document{&MinimalDoc{}}, 145 want: []types.KeySchemaElement{ 146 { 147 AttributeName: aws.String("PK"), 148 KeyType: types.KeyTypeHash, 149 }, 150 { 151 AttributeName: aws.String("SK"), 152 KeyType: types.KeyTypeRange, 153 }, 154 }, 155 }, 156 } 157 for _, tt := range tests { 158 t.Run(tt.name, func(t *testing.T) { 159 s, err := gonetable.NewSchema(tt.docSamples) 160 if err != nil { 161 t.Fatal(err) 162 } 163 if got := s.KeySchema(); !reflect.DeepEqual(got, tt.want) { 164 t.Errorf("Schema.KeySchema() = %v, want %v", got, tt.want) 165 } 166 }) 167 } 168 } 169 170 func TestSchema_GlobalSecondaryIndexes(t *testing.T) { 171 tests := []struct { 172 name string 173 docSamples []gonetable.Document 174 want []types.GlobalSecondaryIndex 175 }{ 176 { 177 name: "minimal", 178 docSamples: []gonetable.Document{&MinimalDoc{}}, 179 want: nil, 180 }, 181 { 182 name: "with index", 183 docSamples: []gonetable.Document{&WithIndex{}}, 184 want: []types.GlobalSecondaryIndex{ 185 { 186 IndexName: aws.String("GSI1"), 187 KeySchema: []types.KeySchemaElement{ 188 { 189 AttributeName: aws.String("GSI1PK"), 190 KeyType: types.KeyTypeHash, 191 }, 192 { 193 AttributeName: aws.String("GSI1SK"), 194 KeyType: types.KeyTypeRange, 195 }, 196 }, 197 Projection: &types.Projection{ 198 ProjectionType: types.ProjectionTypeAll, 199 }, 200 }, 201 }, 202 }, 203 } 204 for _, tt := range tests { 205 t.Run(tt.name, func(t *testing.T) { 206 s, err := gonetable.NewSchema(tt.docSamples) 207 if err != nil { 208 t.Fatal(err) 209 } 210 if got := s.GlobalSecondaryIndexes(); !reflect.DeepEqual(got, tt.want) { 211 t.Errorf("Schema.GlobalSecondaryIndexes() = %v, want %v", got, tt.want) 212 } 213 }) 214 } 215 } 216 217 func TestSchema_Marshal(t *testing.T) { 218 type args struct { 219 docSamples []gonetable.Document 220 doc gonetable.Document 221 } 222 tests := []struct { 223 name string 224 args args 225 want map[string]types.AttributeValue 226 wantErr bool 227 }{ 228 { 229 name: "minimal", 230 args: args{ 231 docSamples: []gonetable.Document{&MinimalDoc{}}, 232 doc: &MinimalDoc{ 233 Name: "hiihaa", 234 }, 235 }, 236 want: map[string]types.AttributeValue{ 237 "Name": MustMarshal("hiihaa"), 238 "PK": MustMarshal("a#b"), 239 "SK": MustMarshal("a#b"), 240 "_Type": MustMarshal("sd1"), 241 }, 242 wantErr: false, 243 }, 244 { 245 name: "with index", 246 args: args{ 247 docSamples: []gonetable.Document{&WithIndex{}}, 248 doc: &WithIndex{ 249 Name: "hiihaa", 250 }, 251 }, 252 want: map[string]types.AttributeValue{ 253 "Name": MustMarshal("hiihaa"), 254 "PK": MustMarshal("wi#hiihaa"), 255 "SK": MustMarshal("wi"), 256 "GSI1PK": MustMarshal("wi#hiihaa"), 257 "GSI1SK": MustMarshal("wi"), 258 "_Type": MustMarshal("wi1"), 259 }, 260 wantErr: false, 261 }, 262 { 263 name: "wrong document type", 264 args: args{ 265 docSamples: []gonetable.Document{&WithIndex{}}, 266 doc: &MinimalDoc{ 267 Name: "hiihaa", 268 }, 269 }, 270 wantErr: true, 271 }, 272 } 273 for _, tt := range tests { 274 t.Run(tt.name, func(t *testing.T) { 275 s, err := gonetable.NewSchema(tt.args.docSamples) 276 if err != nil { 277 t.Fatal(err) 278 } 279 got, err := s.Marshal(tt.args.doc) 280 if (err != nil) != tt.wantErr { 281 t.Errorf("Schema.Marshal() error = %v, wantErr %v", err, tt.wantErr) 282 return 283 } 284 if !reflect.DeepEqual(got, tt.want) { 285 json.NewEncoder(os.Stdout).Encode(got) 286 t.Errorf("Schema.Marshal() = %v, want %v", got, tt.want) 287 } 288 }) 289 } 290 } 291 292 func BenchmarkSchema_Marshal(b *testing.B) { 293 s, err := gonetable.NewSchema([]gonetable.Document{&WithIndex{}}) 294 if err != nil { 295 b.Fatal(err) 296 } 297 d := &WithIndex{ 298 Name: "withindex", 299 } 300 for i := 0; i < 1000; i++ { 301 _, err = s.Marshal(d) 302 if err != nil { 303 b.Fatal(err) 304 } 305 } 306 } 307 308 func BenchmarkSchema_MarshalAlternative(b *testing.B) { 309 s := &AlternativeSchema{} 310 d := &WithIndex{ 311 Name: "withindex", 312 } 313 for i := 0; i < 1000; i++ { 314 _, err := s.alternativeMarshal(d) 315 if err != nil { 316 b.Fatal(err) 317 } 318 } 319 } 320 321 type AlternativeSchema struct { 322 } 323 324 // this would be generated for each document type 325 func (s *AlternativeSchema) alternativeMarshal(doc *WithIndex) (map[string]types.AttributeValue, error) { 326 av, err := attributevalue.MarshalMap(doc) 327 if err != nil { 328 return nil, err 329 } 330 docType := doc.Gonetable_TypeID() 331 av["_Type"], err = attributevalue.Marshal(docType) 332 if err != nil { 333 return nil, err 334 } 335 cKey := doc.Gonetable_Key() 336 cKeyAV, err := cKey.Marshal() 337 if err != nil { 338 return nil, err 339 } 340 for k, v := range cKeyAV { 341 av[k] = v 342 } 343 cGSI1Key := doc.Gonetable_GSI1Key() 344 cGSI1KeyAV, err := cGSI1Key.Marshal() 345 if err != nil { 346 return nil, err 347 } 348 for k, v := range cGSI1KeyAV { 349 av[fmt.Sprintf("GSI1%s", k)] = v 350 } 351 352 return av, nil 353 }