github.com/wheelercj/pm2md@v0.0.11/cmd/generate_text_test.go (about) 1 // Copyright 2023 Chris Wheeler 2 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 7 // http://www.apache.org/licenses/LICENSE-2.0 8 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package cmd 16 17 import ( 18 "os" 19 "reflect" 20 "strings" 21 "testing" 22 ) 23 24 // assertGenerateNoDiff asserts the given JSON and template result in expected 25 // plaintext. If the given template path is empty, the default template is used. 26 // wantPath is the path to an existing file containing the wanted output. 27 func assertGenerateNoDiff(t *testing.T, jsonPath, tmplPath, wantPath string) { 28 // Skip this test if unique file name creation isn't working correctly. 29 TestCreateUniqueFileName(t) 30 TestCreateUniqueFileNamePanic(t) 31 if t.Failed() { 32 return 33 } 34 35 err := AssertGenerateNoDiff(jsonPath, tmplPath, wantPath, nil) 36 if err != nil { 37 t.Error(err) 38 } 39 } 40 41 func TestParseStatusRanges(t *testing.T) { 42 tests := []struct { 43 input string 44 want [][]int 45 }{ 46 {"", nil}, 47 {"200", [][]int{{200, 200}}}, 48 {"200-299", [][]int{{200, 299}}}, 49 {"200-299,400-499", [][]int{{200, 299}, {400, 499}}}, 50 {"200-200", [][]int{{200, 200}}}, 51 } 52 53 for _, test := range tests { 54 t.Run(test.input, func(t *testing.T) { 55 ans, err := parseStatusRanges(test.input) 56 if err != nil { 57 t.Error(err) 58 return 59 } 60 if !reflect.DeepEqual(ans, test.want) { 61 t.Errorf("parseStatusRanges(%q) = %v, want %v", test.input, ans, test.want) 62 return 63 } 64 }) 65 } 66 } 67 68 func TestParseStatusRangesWithInvalidInput(t *testing.T) { 69 inputs := []string{"200-299-300", "a-299", "200-b", "200-", "-299", "-", "a"} 70 for _, input := range inputs { 71 t.Run(input, func(t *testing.T) { 72 if statusRanges, err := parseStatusRanges(input); err == nil { 73 t.Errorf("parseStatusRanges(%q) = (%v, nil), want non-nil error", input, statusRanges) 74 } 75 }) 76 } 77 } 78 79 func TestParseEmptyCollection(t *testing.T) { 80 collection, err := parseCollection([]byte("")) 81 if err == nil { 82 t.Errorf("parseCollection([]byte(\"\")) = (%v, %v), want (nil, error)", collection, err) 83 } 84 } 85 86 func TestGenerateText(t *testing.T) { 87 inputPath := "../samples/calendar-API.postman_collection.json" 88 wantOutputPath := "../samples/calendar-API-v1.md" 89 assertGenerateNoDiff(t, inputPath, "", wantOutputPath) 90 } 91 92 func TestGenerateTextWithCustomTemplate(t *testing.T) { 93 inputPath := "../samples/minimal-calendar-API.postman_collection.json" 94 customTmplPath := "../samples/custom.tmpl" 95 wantOutputPath := "../samples/custom-calendar-API-v1.md" 96 assertGenerateNoDiff(t, inputPath, customTmplPath, wantOutputPath) 97 } 98 99 func TestGenerateTextWithMinimalTemplate(t *testing.T) { 100 inputPath := "../samples/minimal-calendar-API.postman_collection.json" 101 customTmplPath := "minimal.tmpl" 102 wantOutputPath := "../samples/minimal-calendar-API-v1.md" 103 assertGenerateNoDiff(t, inputPath, customTmplPath, wantOutputPath) 104 } 105 106 func TestParseCollectionWithInvalidJson(t *testing.T) { 107 invalidJson := []byte(` 108 { 109 "info": { 110 "_postman_id": "23799766-64ba-4c7c-aaa9-0d880964db54", 111 "name": "calendar API", 112 "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", 113 "_exporter_id": "23363106" 114 }, 115 `) 116 117 _, err := parseCollection(invalidJson) 118 if err == nil { 119 t.Error("Error expected") 120 } 121 } 122 123 func TestParseCollectionWithOldSchema(t *testing.T) { 124 inputPath := "../samples/calendar-API.postman_collection.json" 125 jsonBytes, err := os.ReadFile(inputPath) 126 if err != nil { 127 t.Error(err) 128 return 129 } 130 jsonStr := string(jsonBytes) 131 132 v210Url := "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 133 v200Url := "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" 134 if !strings.Contains(jsonStr, v210Url) { 135 t.Error("The given JSON doesn't contain the expected URL") 136 return 137 } 138 jsonStr = strings.Replace(jsonStr, v210Url, v200Url, 1) 139 140 if collection, err := parseCollection([]byte(jsonStr)); err == nil { 141 t.Errorf("want (nil, error), got a nil error and a non-nil collection: %v", collection) 142 } 143 } 144 145 // getCollection loads JSON from the file at the given path and converts the JSON to a 146 // map. 147 func getCollection(t *testing.T, jsonPath string) (map[string]any, error) { 148 jsonBytes, err := os.ReadFile(jsonPath) 149 if err != nil { 150 return nil, err 151 } 152 153 collection, err := parseCollection(jsonBytes) 154 if err != nil { 155 return nil, err 156 } 157 158 return collection, nil 159 } 160 161 // assertAllStatuses200 asserts that every "response" object in the given items has a 162 // status code of 200. 163 func assertAllStatuses200(t *testing.T, items []any) { 164 for _, itemAny := range items { 165 item := itemAny.(map[string]any) 166 if subItemsAny, ok := item["item"]; ok { // if item is a folder 167 assertAllStatuses200(t, subItemsAny.([]any)) 168 } else { // if item is an endpoint 169 for _, responseAny := range item["response"].([]any) { 170 response := responseAny.(map[string]any) 171 code := int(response["code"].(float64)) 172 if code != 200 { 173 t.Errorf("want 200, got %d", code) 174 } 175 } 176 } 177 } 178 } 179 180 // assertLevels asserts that each "item" and "response" object has a "level" integer 181 // property, and that it has the expected value. 182 func assertLevels(t *testing.T, items []any, wantLevel int) { 183 for _, itemAny := range items { 184 item := itemAny.(map[string]any) 185 if ansLevel, ok := item["level"]; !ok { 186 t.Errorf("Item %q at level %d has no \"level\" property", item["name"], wantLevel) 187 } else if ansLevel != wantLevel { 188 t.Errorf("Item %q has level %d, want level %d", item["name"], ansLevel, wantLevel) 189 } 190 if subItemsAny, ok := item["item"]; ok { // if item is a folder 191 assertLevels(t, subItemsAny.([]any), wantLevel+1) 192 } else { // if item is an endpoint 193 for _, responseAny := range item["response"].([]any) { 194 response := responseAny.(map[string]any) 195 if ansLevel, ok := response["level"]; !ok { 196 t.Errorf("Endpoint %q at level %d has no \"level\" property", item["name"], wantLevel) 197 } else if ansLevel != wantLevel { 198 t.Errorf("Endpoint %q has level %d, want level %d", item["name"], ansLevel, wantLevel) 199 } 200 } 201 } 202 } 203 } 204 205 func TestFilterResponses(t *testing.T) { 206 jsonPath := "../samples/calendar-API.postman_collection.json" 207 collection, err := getCollection(t, jsonPath) 208 if err != nil { 209 t.Error(err) 210 return 211 } 212 213 filterResponsesByStatus(collection, [][]int{{200, 200}}) 214 items := collection["item"].([]any) 215 assertAllStatuses200(t, items) 216 } 217 218 func TestFilterResponsesWithFolders(t *testing.T) { 219 jsonPath := "../samples/calendar-API.postman_collection.json" 220 collection, err := getCollection(t, jsonPath) 221 if err != nil { 222 t.Error(err) 223 return 224 } 225 226 filterResponsesByStatus(collection, [][]int{{200, 200}}) 227 items := collection["item"].([]any) 228 assertAllStatuses200(t, items) 229 } 230 231 func TestAddLevelProperty(t *testing.T) { 232 jsonPath := "../samples/calendar-API.postman_collection.json" 233 collection, err := getCollection(t, jsonPath) 234 if err != nil { 235 t.Error(err) 236 return 237 } 238 239 addLevelProperty(collection) 240 items := collection["item"].([]any) 241 assertLevels(t, items, 1) 242 } 243 244 func TestGetDestFileStdout(t *testing.T) { 245 destFile, destName, err := openDestFile("-", "", false) 246 if destFile != os.Stdout || destName != "-" || err != nil { 247 t.Errorf("openDestFile(\"-\", \"\") = (%p, %q, %q), want (%p, \"-\", nil)", destFile, destName, err, os.Stdout) 248 } 249 } 250 251 func TestGetDestFileExistingFileErr(t *testing.T) { 252 destFile, destName, err := openDestFile("../LICENSE", "", false) 253 if err == nil { 254 t.Errorf("openDestFile(\"../LICENSE\", \"\", false) = (%p, %q, nil), want non-nil error", destFile, destName) 255 if destName != "-" { 256 destFile.Close() 257 } 258 } 259 } 260 261 func TestGetDestFile(t *testing.T) { 262 tests := []struct { 263 originalDestName, collectionName, wantName string 264 }{ 265 {"", "web API", "web-API.md"}, 266 {"my-API.md", "a collection name", "my-API.md"}, 267 } 268 269 for _, test := range tests { 270 t.Run(test.collectionName, func(t *testing.T) { 271 destFile, destName, err := openDestFile(test.originalDestName, test.collectionName, false) 272 if err != nil { 273 t.Errorf( 274 "openDestFile(%q, %q) = (%p, %q, %v), want nil error", 275 test.originalDestName, test.collectionName, destFile, destName, err, 276 ) 277 return 278 } 279 if destFile == os.Stdout { 280 t.Errorf( 281 "openDestFile(%q, %q) = (os.Stdout, %q, nil), want non-std file", 282 test.originalDestName, test.collectionName, destName, 283 ) 284 return 285 } 286 if destFile == os.Stdin { 287 t.Errorf( 288 "openDestFile(%q, %q) = (os.Stdin, %q, nil), want non-std file", 289 test.originalDestName, test.collectionName, destName, 290 ) 291 return 292 } 293 if destFile == os.Stderr { 294 t.Errorf( 295 "openDestFile(%q, %q) = (os.Stderr, %q, nil), want non-std file", 296 test.originalDestName, test.collectionName, destName, 297 ) 298 return 299 } 300 destFile.Close() 301 defer os.Remove(destName) 302 if destName != test.wantName { 303 t.Errorf( 304 "openDestFile(%q, %q) = (%p, %q, nil), want (%p, %q, nil)", 305 test.originalDestName, test.collectionName, destFile, destName, destFile, test.wantName, 306 ) 307 } 308 }) 309 } 310 } 311 312 func TestGetDestFileWithEmptyNames(t *testing.T) { 313 wantDestName := "collection.md" 314 destFile, destName, err := openDestFile("", "", false) 315 if err != nil || destName != wantDestName || destFile == nil { 316 t.Errorf("openDestFile(\"\", \"\") = (%p, %q, %v), want (non-nil *os.File, %q, nil)", destFile, destName, err, wantDestName) 317 } 318 if destFile == os.Stdout { 319 t.Error("openDestFile(\"\", \"\") returned os.Stdout, want non-std file pointer") 320 } else if destFile == os.Stdin { 321 t.Error("openDestFile(\"\", \"\") returned os.Stdin, want non-std file pointer") 322 } else if destFile == os.Stderr { 323 t.Error("openDestFile(\"\", \"\") returned os.Stderr, want non-std file pointer") 324 } else if err == nil { 325 destFile.Close() 326 os.Remove(destName) 327 } 328 } 329 330 func TestGetDestFileNameReplaceError(t *testing.T) { 331 destFile, destName, err := openDestFile("samples/calendar-API-v1.md", "", false) 332 if err == nil { 333 t.Errorf("openDestFile targeting an existing file returned nil error, want non-nil error") 334 t.Errorf("openDestFile(<existing file>, \"\") = (%p, %q, nil), want (nil, \"\", <non-nil error>)", destFile, destName) 335 if destName != "-" { 336 destFile.Close() 337 } 338 } 339 } 340 341 func TestExecuteTmplWithInvalidTemplate(t *testing.T) { 342 err := executeTmpl(nil, nil, "api v1", "# {{ .Name ") 343 if err == nil { 344 t.Errorf("executeTmpl(nil, nil, \"api v1\", \"# {{ .Name \") = nil, want non-nil error") 345 } 346 }