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  }