File: //proc/thread-self/root/opt/go/pkg/mod/github.com/go-openapi/
[email protected]/normalizer_test.go
package spec
import (
"os"
"path"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const windowsOS = "windows"
// only used for windows
var currentDriveLetter = getCurrentDrive()
// get the current drive letter in lowercase on windows that the test is running
func getCurrentDrive() string {
if runtime.GOOS != windowsOS {
return ""
}
p, err := filepath.Abs("/")
if err != nil {
panic(err)
}
if len(p) == 0 {
panic("current windows drive letter is empty")
}
return strings.ToLower(string(p[0]))
}
func TestNormalizer_NormalizeURI(t *testing.T) {
type testNormalizePathsTestCases []struct {
refPath string
base string
expOutput string
windows bool
nonWindows bool
}
testCases := func() testNormalizePathsTestCases {
return testNormalizePathsTestCases{
{
// http basePath, absolute refPath
refPath: "",
base: "http://www.example.com/base/path/swagger.json",
expOutput: "http://www.example.com/base/path/swagger.json",
},
{
// http basePath, absolute refPath
refPath: "#",
base: "http://www.example.com/base/path/swagger.json",
expOutput: "http://www.example.com/base/path/swagger.json",
},
{
// http basePath, absolute refPath
refPath: "#/definitions/Pet",
base: "http://www.example.com/base/path/swagger.json",
expOutput: "http://www.example.com/base/path/swagger.json#/definitions/Pet",
},
{
// http basePath, absolute refPath
refPath: "http://www.anotherexample.com/another/base/path/swagger.json#/definitions/Pet",
base: "http://www.example.com/base/path/swagger.json",
expOutput: "http://www.anotherexample.com/another/base/path/swagger.json#/definitions/Pet",
},
{
// http basePath, relative refPath
refPath: "another/base/path/swagger.json#/definitions/Pet",
base: "http://www.example.com/base/path/swagger.json",
expOutput: "http://www.example.com/base/path/another/base/path/swagger.json#/definitions/Pet",
},
{
// file basePath, absolute refPath, no fragment
refPath: "/another/base/path.json",
base: "/base/path.json",
expOutput: "/another/base/path.json",
},
{
// path clean
refPath: "/another///base//path.json/",
base: "/base/path.json",
expOutput: "/another/base/path.json",
},
{
// path clean edge case
refPath: "",
base: "/base/path.json",
expOutput: "/base/path.json",
},
{
// file basePath, absolute refPath
refPath: "/another/base/path.json#/definitions/Pet",
base: "/base/path.json",
expOutput: "/another/base/path.json#/definitions/Pet",
},
{
// file basePath, relative refPath
refPath: "another/base/path.json#/definitions/Pet",
base: "/base/path.json",
expOutput: "/base/another/base/path.json#/definitions/Pet",
},
{
// file basePath, relative refPath
refPath: "./another/base/path.json#/definitions/Pet",
base: "/base/path.json",
expOutput: "/base/another/base/path.json#/definitions/Pet",
},
{
refPath: "another/base/path.json#/definitions/Pet",
base: "file:///base/path.json",
expOutput: "file:///base/another/base/path.json#/definitions/Pet",
},
{
refPath: "/another/base/path.json#/definitions/Pet",
base: "https://www.example.com:8443//base/path.json",
expOutput: "https://www.example.com:8443/another/base/path.json#/definitions/Pet",
},
{
// params in base
refPath: "/another/base/path.json#/definitions/Pet",
base: "https://www.example.com:8443//base/path.json?raw=true",
expOutput: "https://www.example.com:8443/another/base/path.json?raw=true#/definitions/Pet",
},
{
// params in ref
refPath: "https://origin.com/another/file.json?raw=true",
base: "https://www.example.com:8443//base/path.json?raw=true",
expOutput: "https://origin.com/another/file.json?raw=true",
},
{
refPath: "another/base/def.yaml#/definitions/Pet",
base: "file:///base/path.json",
expOutput: "file:///base/another/base/def.yaml#/definitions/Pet",
},
{
refPath: "",
base: "file:///base/path.json",
expOutput: "file:///base/path.json",
},
{
refPath: "#",
base: "file:///base/path.json",
expOutput: "file:///base/path.json",
},
{
refPath: "../other/another.json#/definitions/X",
base: "file:///base/path.json",
expOutput: "file:///other/another.json#/definitions/X",
},
{
// invalid URI
refPath: "\x7f\x9a",
base: "file:///base/path.json",
expOutput: "file:///base/path.json",
},
{
// file basePath, absolute refPath, no fragment
refPath: `C:\another\base\path.json`,
base: `file:///c:/base/path.json`,
expOutput: `file:///c:/another/base/path.json`,
windows: true,
},
{
// file basePath, absolute refPath
refPath: `C:\another\Base\path.json#/definitions/Pet`,
base: `file:///c:/base/path.json`,
expOutput: `file:///c:/another/base/path.json#/definitions/Pet`,
windows: true,
},
{
// file basePath, relative refPath
refPath: `another\base\path.json#/definitions/Pet`,
base: `file:///c:/base/path.json`,
expOutput: `file:///c:/base/another/base/path.json#/definitions/Pet`,
windows: true,
},
{
// file basePath, relative refPath
refPath: `.\another\base\path.json#/definitions/Pet`,
base: `file:///c:/base/path.json`,
expOutput: `file:///c:/base/another/base/path.json#/definitions/Pet`,
windows: true,
},
{
refPath: `\\host\share\another\base\path.json#/definitions/Pet`,
base: `file:///c:/base/path.json`,
expOutput: `file://host/share/another/base/path.json#/definitions/Pet`,
windows: true,
},
{
// repair URI
refPath: `file://E:\Base\sub\File.json`,
base: `file:///c:/base/path.json`,
expOutput: `file:///e:/base/sub/file.json`,
windows: true,
},
{
// case sensitivity on local paths only (1/4)
// see note:
refPath: `Resources.yaml#/definitions/Pets`,
base: `file:///c:/base/Spec.json`,
expOutput: `file:///c:/base/Resources.yaml#/definitions/Pets`,
windows: true,
},
{
// case sensitivity on local paths only (2/4)
refPath: `Resources.yaml#/definitions/Pets`,
base: `file:///c:/base/Spec.json`,
expOutput: `file:///c:/base/Resources.yaml#/definitions/Pets`,
nonWindows: true,
},
{
// case sensitivity on local paths only (3/4)
refPath: `Resources.yaml#/definitions/Pets`,
base: `https://example.com//base/Spec.json`,
expOutput: `https://example.com/base/Resources.yaml#/definitions/Pets`,
},
{
// escaped characters in base (1)
refPath: `Resources.yaml#/definitions/Pets`,
base: `https://example.com/base (x86)/Spec.json`,
expOutput: `https://example.com/base%20%28x86%29/Resources.yaml#/definitions/Pets`,
},
{
// escaped characters in base (2)
refPath: `Resources.yaml#/definitions/Pets`,
base: `https://example.com/base [x86]/Spec.json`,
expOutput: `https://example.com/base%20%5Bx86%5D/Resources.yaml#/definitions/Pets`,
},
{
// escaped characters in joined fragment
refPath: `Resources.yaml#/definitions (x86)/Pets`,
base: `https://example.com/base/Spec.json`,
expOutput: `https://example.com/base/Resources.yaml#/definitions%20(x86)/Pets`,
},
{
// escaped characters in joined path
refPath: `Resources [x86].yaml#/definitions/Pets`,
base: `https://example.com/base/Spec.json`,
expOutput: `https://example.com/base/Resources%20%5Bx86%5D.yaml#/definitions/Pets`,
},
}
}()
for _, toPin := range testCases {
testCase := toPin
if testCase.windows && runtime.GOOS != windowsOS {
continue
}
if testCase.nonWindows && runtime.GOOS == windowsOS {
continue
}
t.Run(testCase.refPath, func(t *testing.T) {
t.Parallel()
out := normalizeURI(testCase.refPath, testCase.base)
assert.Equalf(t, testCase.expOutput, out,
"unexpected normalized URL with $ref %q and base %q", testCase.refPath, testCase.base)
})
}
}
func TestNormalizer_NormalizeBase(t *testing.T) {
cwd, err := os.Getwd()
require.NoError(t, err)
if runtime.GOOS == windowsOS {
cwd = "/" + strings.ToLower(filepath.ToSlash(cwd))
}
const fileScheme = "file:///"
for _, toPin := range []struct {
Base, Expected string
Windows bool
NonWindows bool
}{
{
Base: "",
Expected: "file://$cwd", // edge case: this won't work because a document is a file
},
{
Base: "#",
Expected: "file://$cwd", // edge case: this won't work because a document is a file
},
{
Base: "\x7f\x9a",
Expected: "file://$cwd", // edge case: invalid URI
},
{
Base: ".",
Expected: "file://$cwd", // edge case: this won't work because a document is a file
},
{
Base: "https://user:[email protected]:123/base/sub/file.json",
Expected: "https://user:[email protected]:123/base/sub/file.json",
},
{
// irrelevant fragment: cleaned
Base: "http://www.anotherexample.com/another/base/path/swagger.json#/definitions/Pet",
Expected: "http://www.anotherexample.com/another/base/path/swagger.json",
},
{
Base: "base/sub/file.json",
Expected: "file://$cwd/base/sub/file.json",
},
{
Base: "./base/sub/file.json",
Expected: "file://$cwd/base/sub/file.json",
},
{
Base: "file:/base/sub/file.json",
Expected: "file:///base/sub/file.json",
},
{
// funny scheme, no path
Base: "smb://host",
Expected: "smb://host",
},
{
// explicit scheme, with host and path
Base: "gs://bucket/folder/file.json",
Expected: "gs://bucket/folder/file.json",
},
{
// explicit file scheme, with host and path
Base: "file://folder/file.json",
Expected: "file://folder/file.json",
},
{
// path clean
Base: "file:///folder//subfolder///file.json/",
Expected: "file:///folder/subfolder/file.json",
},
{
// path clean
Base: "///folder//subfolder///file.json/",
Expected: "file:///folder/subfolder/file.json",
NonWindows: true,
},
{
// path clean
Base: "///folder//subfolder///file.json/",
Expected: fileScheme + currentDriveLetter + ":/folder/subfolder/file.json",
Windows: true,
},
{
// relevant query param: kept
Base: "https:///host/base/sub/file.json?query=param",
Expected: "https:///host/base/sub/file.json?query=param",
},
{
// no host component, absolute path
Base: `file:/base/sub/file.json`,
Expected: "file:///base/sub/file.json",
},
{
// handling dots (1/6): dodgy specification - resolved to /
Base: `file:///.`,
Expected: "file:///",
},
{
// handling dots (2/6): valid, cleaned to /
Base: "/..",
Expected: "file:///",
NonWindows: true,
},
{
// handling dots (3/6): valid, cleaned to /c:/ on windows
Base: "/..",
Expected: fileScheme + currentDriveLetter + ":",
Windows: true,
},
{
// handling dots (4/6): dodgy specification - resolved to /
Base: `file:/.`,
Expected: fileScheme,
},
{
// handling dots (5/6): dodgy specification - resolved to /
Base: `file:/..`,
Expected: fileScheme,
},
{
// handling dots (6/6)
Base: `..`,
Expected: "file://$dir",
},
// non-windows case
{
Base: "/base/sub/file.json",
Expected: "file:///base/sub/file.json",
NonWindows: true,
},
{
// irrelevant query param (local file resolver): cleaned
Base: "/base/sub/file.json?query=param",
Expected: "file:///base/sub/file.json",
NonWindows: true,
},
// windows-only cases
{
Base: "/base/sub/file.json",
Expected: fileScheme + currentDriveLetter + ":/base/sub/file.json", // on windows, filepath.Abs("/a/b") prepends the "c:" drive
Windows: true,
},
{
// case sensitivity
Base: `C:\Base\sub\File.json`,
Expected: "file:///c:/base/sub/file.json",
Windows: true,
},
{
// This one is parsed correctly: notice the third slash
Base: `file:///\Base\sub\File.json`,
Expected: "file:///base/sub/file.json",
Windows: true,
},
{
// absolute path
Base: `file:/\Base\sub\File.json`,
Expected: "file:///base/sub/file.json",
Windows: true,
},
{
// windows UNC path, no drive
Base: `\\host\share@1234\Folder\File.json`,
Expected: "file://host/share@1234/folder/file.json",
Windows: true,
},
{
// repair invalid use of leading "." on windows
Base: `file:///.\Base\sub\File.json`,
Expected: "file://$cwd/base/sub/file.json",
Windows: true,
},
{
Base: `file:/E:\Base\sub\File.json`,
Expected: "file:///e:/base/sub/file.json",
Windows: true,
},
{
// repair URI (windows)
// This one exhibits an example of invalid URI (missing a 3rd "/")
Base: `file://E:\Base\sub\File.json`,
Expected: "file:///e:/base/sub/file.json",
Windows: true,
},
{
// escaped characters in base (1)
Base: `file:///c:/base (x86)/spec.json`,
Expected: `file:///c:/base%20%28x86%29/spec.json`,
},
} {
testCase := toPin
if testCase.Windows && runtime.GOOS != windowsOS {
continue
}
if testCase.NonWindows && runtime.GOOS == windowsOS {
continue
}
t.Run(testCase.Base, func(t *testing.T) {
t.Parallel()
expected := strings.ReplaceAll(strings.ReplaceAll(testCase.Expected, "$cwd", cwd), "$dir", path.Dir(cwd))
require.Equalf(t, expected, normalizeBase(testCase.Base), "for base %q", testCase.Base)
// check for idempotence
require.Equalf(t, expected, normalizeBase(normalizeBase(testCase.Base)),
"expected idempotent behavior on base %q", testCase.Base)
})
}
}
func TestNormalizer_Denormalize(t *testing.T) {
cwd, err := os.Getwd()
require.NoError(t, err)
for _, toPin := range []struct {
OriginalBase, Ref, Expected, ID string
Windows bool
NonWindows bool
}{
{
OriginalBase: "file:///a/b/c/file.json",
Ref: "#/definitions/X",
Expected: "#/definitions/X",
},
{
OriginalBase: "file:///a/b/c/file.json#ignoredFragment",
Ref: "#/definitions/X",
Expected: "#/definitions/X",
},
{
OriginalBase: "https://user:[email protected]/a/b/c/file.json",
Ref: "https://user:[email protected]/a/b/c/other.json#/definitions/X",
Expected: "other.json#/definitions/X",
},
{
OriginalBase: "file:///a/b/c/file.json",
Ref: "file:///a/b/c/items.json#/definitions/X",
Expected: "items.json#/definitions/X",
},
{
OriginalBase: "file:///a/b/c/file.json",
Ref: "https:///x/y/z/items.json#/definitions/X",
Expected: "https:///x/y/z/items.json#/definitions/X",
},
{
OriginalBase: "file:///a/b/c/file.json",
Ref: "file:///a/b/c/file.json#/definitions/X",
Expected: "#/definitions/X",
},
{
OriginalBase: "file:///a/b/c/file.json",
Ref: "file:///a/b/c/d/other.json#/definitions/X",
Expected: "d/other.json#/definitions/X",
},
{
OriginalBase: "file:///a/b/c/file.json",
Ref: "file:///a/b/c/file.json#",
Expected: "",
},
{
OriginalBase: "file:///a/b/c/file.json",
Ref: "file:///a/b/c/d/file.json",
Expected: "d/file.json",
},
{
OriginalBase: "file:///a/b/c/file.json",
Ref: "file:///a/b/c/file.json",
Expected: "",
},
{
OriginalBase: "file:///a/b/c/file.json",
Ref: "file:///a/b/c/../other.json#/definitions/X",
Expected: "../other.json#/definitions/X",
},
{
OriginalBase: "file:///a/b/c/file.json",
Ref: "file:///a/b/c/../../other.json#/definitions/X",
Expected: "../../other.json#/definitions/X",
},
{
// we may end up in this situation following ../.. in paths
OriginalBase: "file:///a1/b/c/file.json",
Ref: "file:///a2/b/c/file.json#/definitions/X",
Expected: "file:///a2/b/c/file.json#/definitions/X",
},
{
OriginalBase: "file:///file.json",
Ref: "file:///file.json#/definitions/X",
Expected: "#/definitions/X",
},
{
OriginalBase: "file://host/file.json",
Ref: "file://host/file.json#/definitions/X",
Expected: "#/definitions/X",
},
{
OriginalBase: "file://host1/file.json",
Ref: "file://host2/file.json#/definitions/X",
Expected: "file://host2/file.json#/definitions/X",
},
{
OriginalBase: "file:///a/b/c/file.json",
ID: "https://myschema/",
Ref: "file:///a/b/c/file.json#/definitions/X",
Expected: "#/definitions/X",
},
{
OriginalBase: "file:///a/b/c/file.json",
ID: "https://myschema/",
Ref: "https://myschema#/definitions/X",
Expected: "#/definitions/X",
},
{
OriginalBase: "file:///a/b/c/file.json",
ID: "https://myschema/",
Ref: "https://myschema#/definitions/X",
Expected: "#/definitions/X",
},
{
OriginalBase: "file:///folder/file.json",
ID: "https://example.com/schema",
Ref: "https://example.com/schema#/definitions/X",
Expected: "#/definitions/X",
},
{
OriginalBase: "file:///folder/file.json",
ID: "https://example.com/schema",
Ref: "https://example.com/schema/other-file.json#/definitions/X",
Expected: "https://example.com/other-file.json#/definitions/X",
},
} {
testCase := toPin
if testCase.Windows && runtime.GOOS != windowsOS { // windows only
continue
}
if testCase.NonWindows && runtime.GOOS == windowsOS { // non-windows only
continue
}
t.Run(testCase.Ref, func(t *testing.T) {
t.Parallel()
expected := strings.ReplaceAll(testCase.Expected, "$cwd", cwd)
ref := MustCreateRef(testCase.Ref)
newRef := denormalizeRef(&ref, testCase.OriginalBase, testCase.ID)
require.NotNil(t, newRef)
require.Equalf(t, expected, newRef.String(),
"expected %s, but got %s", testCase.Expected, newRef.String())
})
}
}