Skip to content

Commit ef776d5

Browse files
test: windows
1 parent aa189c0 commit ef776d5

File tree

5 files changed

+238
-11
lines changed

5 files changed

+238
-11
lines changed

internal/cache/manager_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,9 @@ func TestClearFieldCache_Success(t *testing.T) {
8787
assert.Equal(t, int64(0), stats.FieldCacheSize, "Field cache should be empty")
8888
}
8989

90+
// nolint:paralleltest
9091
func TestGetAllCacheStats_Success(t *testing.T) {
91-
t.Parallel()
92+
// Don't run in parallel since we're testing global cache state
9293

9394
// Clear all caches first
9495
ClearAllCaches()

internal/utils/references.go

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -183,19 +183,89 @@ func (rc *ReferenceClassification) joinURL(relative string) (string, error) {
183183
return resolvedURL.String(), nil
184184
}
185185

186-
// joinFilePath joins this file path reference with a relative path using Go's filepath package
186+
// joinFilePath joins this file path reference with a relative path using cross-platform path handling
187187
func (rc *ReferenceClassification) joinFilePath(relative string) (string, error) {
188188
// If relative path is absolute, return it as-is
189189
// Check for both OS-specific absolute paths and Unix-style absolute paths (for cross-platform compatibility)
190-
if filepath.IsAbs(relative) || strings.HasPrefix(relative, "/") {
190+
if filepath.IsAbs(relative) || strings.HasPrefix(relative, "/") || rc.isWindowsAbsolutePath(relative) {
191191
return relative, nil
192192
}
193193

194-
// For all relative paths, join them with the base directory
195-
// Use filepath.Join for proper path handling, then convert to forward slashes for OpenAPI/JSON Schema compatibility
196-
joined := filepath.Join(filepath.Dir(rc.Original), relative)
197-
// Convert backslashes to forward slashes for cross-platform compatibility in OpenAPI contexts
198-
return strings.ReplaceAll(joined, "\\", "/"), nil
194+
// Determine the path separator style from the original path
195+
isWindowsStyle := strings.Contains(rc.Original, "\\") && !strings.Contains(rc.Original, "/")
196+
197+
// Get the directory part of the original path using cross-platform logic
198+
var baseDir string
199+
if isWindowsStyle {
200+
// Handle Windows-style paths manually for cross-platform compatibility
201+
baseDir = rc.getWindowsDir()
202+
} else {
203+
// Use standard filepath.Dir for Unix-style paths
204+
baseDir = filepath.Dir(rc.Original)
205+
}
206+
207+
// Join the paths
208+
var joined string
209+
if isWindowsStyle {
210+
// Manual Windows-style path joining
211+
joined = rc.joinWindowsPaths(baseDir, relative)
212+
} else {
213+
// Use standard filepath.Join for Unix-style paths
214+
joined = filepath.Join(baseDir, relative)
215+
// Convert to forward slashes for OpenAPI/JSON Schema compatibility
216+
joined = strings.ReplaceAll(joined, "\\", "/")
217+
}
218+
219+
return joined, nil
220+
}
221+
222+
// getWindowsDir extracts the directory part from a Windows-style path
223+
func (rc *ReferenceClassification) getWindowsDir() string {
224+
path := rc.Original
225+
// Find the last backslash
226+
lastSlash := strings.LastIndex(path, "\\")
227+
if lastSlash == -1 {
228+
return "." // No directory separator found
229+
}
230+
return path[:lastSlash]
231+
}
232+
233+
// joinWindowsPaths joins Windows-style paths manually
234+
func (rc *ReferenceClassification) joinWindowsPaths(base, relative string) string {
235+
// Handle relative path navigation
236+
parts := strings.Split(relative, "\\")
237+
baseParts := strings.Split(base, "\\")
238+
239+
for _, part := range parts {
240+
switch part {
241+
case ".":
242+
// Current directory, do nothing
243+
continue
244+
case "..":
245+
// Parent directory, remove last part from base
246+
if len(baseParts) > 1 {
247+
baseParts = baseParts[:len(baseParts)-1]
248+
}
249+
default:
250+
// Regular path component
251+
baseParts = append(baseParts, part)
252+
}
253+
}
254+
255+
return strings.Join(baseParts, "\\")
256+
}
257+
258+
// isWindowsAbsolutePath checks if a path is a Windows absolute path (e.g., C:\path or \\server\share)
259+
func (rc *ReferenceClassification) isWindowsAbsolutePath(path string) bool {
260+
// Check for drive letter paths (C:\, D:\, etc.)
261+
if len(path) >= 3 && path[1] == ':' && (path[2] == '\\' || path[2] == '/') {
262+
return true
263+
}
264+
// Check for UNC paths (\\server\share)
265+
if strings.HasPrefix(path, "\\\\") {
266+
return true
267+
}
268+
return false
199269
}
200270

201271
// JoinReference is a convenience function that classifies the base reference and joins it with a relative reference.
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package utils
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
// TestWindowsStylePathJoining tests Windows-style path joining logic
11+
// This test simulates the Windows path behavior to verify our fixes
12+
func TestWindowsStylePathJoining_Success(t *testing.T) {
13+
t.Parallel()
14+
15+
tests := []struct {
16+
name string
17+
base string
18+
relative string
19+
expected string
20+
}{
21+
{
22+
name: "windows path with simple relative file",
23+
base: "C:\\path\\to\\schemas\\user.json",
24+
relative: "common.json",
25+
expected: "C:\\path\\to\\schemas\\common.json",
26+
},
27+
{
28+
name: "windows path with relative directory navigation",
29+
base: "C:\\path\\to\\schemas\\user.json",
30+
relative: "..\\base\\common.json",
31+
expected: "C:\\path\\to\\base\\common.json",
32+
},
33+
{
34+
name: "windows path with dot relative path",
35+
base: "C:\\path\\to\\schemas\\user.json",
36+
relative: ".\\common.json",
37+
expected: "C:\\path\\to\\schemas\\common.json",
38+
},
39+
{
40+
name: "windows path with absolute relative path",
41+
base: "C:\\path\\to\\schemas\\user.json",
42+
relative: "D:\\other\\path\\schema.json",
43+
expected: "D:\\other\\path\\schema.json",
44+
},
45+
{
46+
name: "windows path with fragment",
47+
base: "C:\\path\\to\\schema.json",
48+
relative: "#/definitions/User",
49+
expected: "C:\\path\\to\\schema.json#/definitions/User",
50+
},
51+
{
52+
name: "windows UNC path joining",
53+
base: "\\\\server\\share\\path\\base.json",
54+
relative: "schema.json",
55+
expected: "\\\\server\\share\\path\\schema.json",
56+
},
57+
}
58+
59+
for _, tt := range tests {
60+
t.Run(tt.name, func(t *testing.T) {
61+
t.Parallel()
62+
63+
classification, err := ClassifyReference(tt.base)
64+
require.NoError(t, err)
65+
require.NotNil(t, classification)
66+
require.True(t, classification.IsFile, "Base should be classified as file path")
67+
68+
result, err := classification.JoinWith(tt.relative)
69+
require.NoError(t, err)
70+
assert.Equal(t, tt.expected, result)
71+
})
72+
}
73+
}
74+
75+
// TestWindowsStylePathJoinReference_Success tests the convenience function
76+
func TestWindowsStylePathJoinReference_Success(t *testing.T) {
77+
t.Parallel()
78+
79+
tests := []struct {
80+
name string
81+
base string
82+
relative string
83+
expected string
84+
}{
85+
{
86+
name: "windows path joining via convenience function",
87+
base: "C:\\path\\to\\base.json",
88+
relative: "schema.json",
89+
expected: "C:\\path\\to\\schema.json",
90+
},
91+
{
92+
name: "windows UNC path joining",
93+
base: "\\\\server\\share\\path\\base.json",
94+
relative: "schema.json",
95+
expected: "\\\\server\\share\\path\\schema.json",
96+
},
97+
}
98+
99+
for _, tt := range tests {
100+
t.Run(tt.name, func(t *testing.T) {
101+
t.Parallel()
102+
103+
result, err := JoinReference(tt.base, tt.relative)
104+
require.NoError(t, err)
105+
assert.Equal(t, tt.expected, result)
106+
})
107+
}
108+
}
109+
110+
// TestUnixStylePathJoining_Success tests that Unix-style paths still work correctly
111+
func TestUnixStylePathJoining_Success(t *testing.T) {
112+
t.Parallel()
113+
114+
tests := []struct {
115+
name string
116+
base string
117+
relative string
118+
expected string
119+
}{
120+
{
121+
name: "unix path with simple relative file",
122+
base: "/path/to/schemas/user.json",
123+
relative: "common.json",
124+
expected: "/path/to/schemas/common.json",
125+
},
126+
{
127+
name: "unix path with relative directory navigation",
128+
base: "/path/to/schemas/user.json",
129+
relative: "../base/common.json",
130+
expected: "/path/to/base/common.json",
131+
},
132+
{
133+
name: "unix path with dot relative path",
134+
base: "/path/to/schemas/user.json",
135+
relative: "./common.json",
136+
expected: "/path/to/schemas/common.json",
137+
},
138+
}
139+
140+
for _, tt := range tests {
141+
t.Run(tt.name, func(t *testing.T) {
142+
t.Parallel()
143+
144+
classification, err := ClassifyReference(tt.base)
145+
require.NoError(t, err)
146+
require.NotNil(t, classification)
147+
require.True(t, classification.IsFile, "Base should be classified as file path")
148+
149+
result, err := classification.JoinWith(tt.relative)
150+
require.NoError(t, err)
151+
assert.Equal(t, tt.expected, result)
152+
})
153+
}
154+
}

internal/utils/url_cache_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,10 @@ func TestURLCache_Clear(t *testing.T) {
129129
assert.Equal(t, int64(0), sizeAfter)
130130
}
131131

132+
// nolint:paralleltest
132133
func TestParseURLCached_Global(t *testing.T) {
133-
t.Parallel()
134+
// Don't run in parallel since we're testing global cache state
135+
134136
// Clear global cache before test
135137
ClearGlobalURLCache()
136138

references/resolution_cache_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,8 @@ func TestRefCache_Clear(t *testing.T) {
176176
assert.Equal(t, int64(0), stats.Size)
177177
}
178178

179+
//nolint:paralleltest // This test uses global cache and cannot be parallel
179180
func TestResolveAbsoluteReferenceCached_Global(t *testing.T) {
180-
t.Parallel()
181181
// Clear global cache before test
182182
ClearGlobalRefCache()
183183

@@ -203,8 +203,8 @@ func TestResolveAbsoluteReferenceCached_Global(t *testing.T) {
203203
ClearGlobalRefCache()
204204
}
205205

206+
//nolint:paralleltest // This test uses global cache and cannot be parallel
206207
func TestResolveAbsoluteReference_UsesCache(t *testing.T) {
207-
t.Parallel()
208208
// Clear global cache before test
209209
ClearGlobalRefCache()
210210

0 commit comments

Comments
 (0)