Skip to content

Commit 64dbc1a

Browse files
czunkerclaude
andauthored
🟢 Add Windows-native test coverage for OS provider (#6690)
* 🟢 Add Windows-native test coverage for OS provider Add _windows_test.go files to exercise native Windows code paths (registry API, WMI, IP Helper API, fsutil) that were previously untested. These tests complement the Windows CI workflow from PR #6687. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * 🟢 Fix TestFileResource for Windows: use t.TempDir() instead of /tmp The hardcoded /tmp/test_hash path doesn't exist on Windows. Use t.TempDir() with filepath.Join for a cross-platform temp path. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * 🟢 Fix TestFindFiles for Windows: skip Chmod and permission tests afero MemMapFs Chmod doesn't work on Windows, and Unix file permissions don't apply there. Guard Chmod and permission-based filtering with runtime.GOOS checks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * 🧹 Extract mockLocalConnection to shared testhelpers_windows_test.go Move the shared mock type from build_version_windows_test.go into its own file so the cross-file dependency is self-documenting. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * 🟢 Split permission tests into build-tag-guarded unix-only file Replace runtime.GOOS guards with a //go:build !windows file (find_files_unix_test.go) for chmod/permission tests, keeping find_files_test.go fully cross-platform. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * 🟢 Fix TestFileResource on Windows: close file before TempDir cleanup Add defer f.Close() to prevent Windows file-locking error during t.TempDir() cleanup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d5ffa11 commit 64dbc1a

File tree

9 files changed

+523
-7
lines changed

9 files changed

+523
-7
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) Mondoo, Inc.
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
//go:build windows
5+
6+
package windows
7+
8+
import (
9+
"testing"
10+
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func TestGetWindowsOSBuild_Integration(t *testing.T) {
16+
conn := &mockLocalConnection{}
17+
ver, err := GetWindowsOSBuild(conn)
18+
require.NoError(t, err)
19+
require.NotNil(t, ver)
20+
21+
assert.NotEmpty(t, ver.CurrentBuild, "CurrentBuild should not be empty")
22+
assert.NotEmpty(t, ver.ProductName, "ProductName should not be empty")
23+
assert.NotEmpty(t, ver.Architecture, "Architecture should not be empty")
24+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) Mondoo, Inc.
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
//go:build windows
5+
6+
package windows
7+
8+
import (
9+
"github.com/spf13/afero"
10+
"go.mondoo.com/mql/v13/providers-sdk/v1/inventory"
11+
"go.mondoo.com/mql/v13/providers/os/connection/shared"
12+
)
13+
14+
// mockLocalConnection implements shared.Connection and returns Type_Local.
15+
// It is used by multiple test files in this package.
16+
type mockLocalConnection struct{}
17+
18+
func (m *mockLocalConnection) ID() uint32 { return 0 }
19+
func (m *mockLocalConnection) ParentID() uint32 { return 0 }
20+
func (m *mockLocalConnection) RunCommand(command string) (*shared.Command, error) { return nil, nil }
21+
func (m *mockLocalConnection) FileInfo(path string) (shared.FileInfoDetails, error) {
22+
return shared.FileInfoDetails{}, nil
23+
}
24+
func (m *mockLocalConnection) FileSystem() afero.Fs { return afero.NewOsFs() }
25+
func (m *mockLocalConnection) Name() string { return "mock-local" }
26+
func (m *mockLocalConnection) Type() shared.ConnectionType { return shared.Type_Local }
27+
func (m *mockLocalConnection) Asset() *inventory.Asset { return &inventory.Asset{} }
28+
func (m *mockLocalConnection) UpdateAsset(asset *inventory.Asset) {}
29+
func (m *mockLocalConnection) Capabilities() shared.Capabilities {
30+
return shared.Capability_None
31+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright (c) Mondoo, Inc.
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
//go:build windows
5+
6+
package windows
7+
8+
import (
9+
"testing"
10+
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func TestToString(t *testing.T) {
16+
t.Run("nil returns empty string", func(t *testing.T) {
17+
assert.Equal(t, "", toString(nil))
18+
})
19+
20+
t.Run("non-nil returns value", func(t *testing.T) {
21+
s := "hello"
22+
assert.Equal(t, "hello", toString(&s))
23+
})
24+
25+
t.Run("empty string returns empty string", func(t *testing.T) {
26+
s := ""
27+
assert.Equal(t, "", toString(&s))
28+
})
29+
}
30+
31+
func TestIntToString(t *testing.T) {
32+
t.Run("nil returns empty string", func(t *testing.T) {
33+
assert.Equal(t, "", intToString(nil))
34+
})
35+
36+
t.Run("non-nil returns string representation", func(t *testing.T) {
37+
i := 42
38+
assert.Equal(t, "42", intToString(&i))
39+
})
40+
41+
t.Run("zero returns 0", func(t *testing.T) {
42+
i := 0
43+
assert.Equal(t, "0", intToString(&i))
44+
})
45+
}
46+
47+
func TestGetWmiInformation_Integration(t *testing.T) {
48+
conn := &mockLocalConnection{}
49+
info, err := GetWmiInformation(conn)
50+
require.NoError(t, err)
51+
require.NotNil(t, info)
52+
53+
assert.NotEmpty(t, info.Version, "Version should not be empty")
54+
assert.NotEmpty(t, info.BuildNumber, "BuildNumber should not be empty")
55+
assert.NotEmpty(t, info.Caption, "Caption should not be empty")
56+
}

providers/os/fsutil/find_files_test.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,6 @@ func TestFindFiles(t *testing.T) {
168168
mkFile(t, fs, "root/b/file1")
169169
mkFile(t, fs, "root/c/file4")
170170
mkFile(t, fs, "root/c/d/file5")
171-
require.NoError(t, fs.Chmod("root/c/file4", 0o002))
172171

173172
rootAFiles, err := FindFiles(afero.NewIOFS(fs), "root/a", nil, "f", nil, nil)
174173
require.NoError(t, err)
@@ -186,11 +185,6 @@ func TestFindFiles(t *testing.T) {
186185
require.NoError(t, err)
187186
assert.ElementsMatch(t, file1Files, []string{"root/b/file1", "root/a/file1"})
188187

189-
perm := uint32(0o002)
190-
permFiles, err := FindFiles(afero.NewIOFS(fs), "root", nil, "f", &perm, nil)
191-
require.NoError(t, err)
192-
assert.ElementsMatch(t, permFiles, []string{"root/c/file4"})
193-
194188
depth := 0
195189
depthFiles, err := FindFiles(afero.NewIOFS(fs), "root", nil, "f", nil, &depth)
196190
require.NoError(t, err)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (c) Mondoo, Inc.
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
//go:build !windows
5+
6+
package fsutil
7+
8+
import (
9+
"testing"
10+
11+
"github.com/spf13/afero"
12+
"github.com/stretchr/testify/assert"
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
func TestFindFilesPermissionFilter(t *testing.T) {
17+
fs := afero.NewMemMapFs()
18+
mkDir(t, fs, "root/a")
19+
mkDir(t, fs, "root/b")
20+
mkDir(t, fs, "root/c")
21+
mkDir(t, fs, "root/c/d")
22+
23+
mkFile(t, fs, "root/file0")
24+
mkFile(t, fs, "root/a/file1")
25+
mkFile(t, fs, "root/a/file2")
26+
mkFile(t, fs, "root/b/file1")
27+
mkFile(t, fs, "root/c/file4")
28+
mkFile(t, fs, "root/c/d/file5")
29+
30+
require.NoError(t, fs.Chmod("root/c/file4", 0o002))
31+
32+
perm := uint32(0o002)
33+
permFiles, err := FindFiles(afero.NewIOFS(fs), "root", nil, "f", &perm, nil)
34+
require.NoError(t, err)
35+
assert.ElementsMatch(t, permFiles, []string{"root/c/file4"})
36+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright (c) Mondoo, Inc.
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
//go:build windows
5+
6+
package fsutil
7+
8+
import (
9+
"errors"
10+
"os"
11+
"testing"
12+
13+
"github.com/stretchr/testify/assert"
14+
"github.com/stretchr/testify/require"
15+
)
16+
17+
func TestHandleFsError(t *testing.T) {
18+
tests := []struct {
19+
name string
20+
err error
21+
wantSkip bool
22+
wantErr error
23+
}{
24+
{
25+
name: "nil error",
26+
err: nil,
27+
wantSkip: false,
28+
wantErr: nil,
29+
},
30+
{
31+
name: "permission denied is skipped",
32+
err: os.ErrPermission,
33+
wantSkip: true,
34+
wantErr: nil,
35+
},
36+
{
37+
name: "not exist is skipped",
38+
err: os.ErrNotExist,
39+
wantSkip: true,
40+
wantErr: nil,
41+
},
42+
{
43+
name: "invalid is skipped",
44+
err: os.ErrInvalid,
45+
wantSkip: true,
46+
wantErr: nil,
47+
},
48+
{
49+
name: "other error is propagated",
50+
err: errors.New("some other error"),
51+
wantSkip: true,
52+
wantErr: errors.New("some other error"),
53+
},
54+
}
55+
56+
for _, tt := range tests {
57+
t.Run(tt.name, func(t *testing.T) {
58+
skip, err := handleFsError(tt.err)
59+
assert.Equal(t, tt.wantSkip, skip)
60+
if tt.wantErr != nil {
61+
require.Error(t, err)
62+
assert.Equal(t, tt.wantErr.Error(), err.Error())
63+
} else {
64+
require.NoError(t, err)
65+
}
66+
})
67+
}
68+
}

providers/os/fsutil/hash_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package fsutil_test
55

66
import (
7+
"path/filepath"
78
"testing"
89

910
"github.com/spf13/afero"
@@ -14,7 +15,7 @@ import (
1415
)
1516

1617
func TestFileResource(t *testing.T) {
17-
path := "/tmp/test_hash"
18+
path := filepath.Join(t.TempDir(), "test_hash")
1819

1920
conn := local.NewConnection(0, &inventory.Config{
2021
Path: path,
@@ -30,6 +31,7 @@ func TestFileResource(t *testing.T) {
3031
f, err := fs.Open(path)
3132
assert.Nil(t, err)
3233
if assert.NotNil(t, f) {
34+
defer f.Close()
3335
assert.Equal(t, path, f.Name(), "they should be equal")
3436

3537
md5, err := fsutil.Md5(f)
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright (c) Mondoo, Inc.
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
//go:build windows
5+
6+
package registry
7+
8+
import (
9+
"testing"
10+
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
"golang.org/x/sys/windows/registry"
14+
)
15+
16+
func TestParseRegistryKeyPath(t *testing.T) {
17+
tests := []struct {
18+
name string
19+
path string
20+
wantKey registry.Key
21+
wantPath string
22+
wantErr bool
23+
errContains string
24+
}{
25+
{
26+
name: "HKEY_LOCAL_MACHINE full prefix",
27+
path: `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft`,
28+
wantKey: registry.LOCAL_MACHINE,
29+
wantPath: `SOFTWARE\Microsoft`,
30+
},
31+
{
32+
name: "HKLM short prefix",
33+
path: `HKLM\SOFTWARE\Microsoft`,
34+
wantKey: registry.LOCAL_MACHINE,
35+
wantPath: `SOFTWARE\Microsoft`,
36+
},
37+
{
38+
name: "HKEY_CURRENT_USER full prefix",
39+
path: `HKEY_CURRENT_USER\Software\Classes`,
40+
wantKey: registry.CURRENT_USER,
41+
wantPath: `Software\Classes`,
42+
},
43+
{
44+
name: "HKCU short prefix",
45+
path: `HKCU\Software\Classes`,
46+
wantKey: registry.CURRENT_USER,
47+
wantPath: `Software\Classes`,
48+
},
49+
{
50+
name: "HKEY_USERS prefix",
51+
path: `HKEY_USERS\.DEFAULT`,
52+
wantKey: registry.USERS,
53+
wantPath: `.DEFAULT`,
54+
},
55+
{
56+
name: "invalid hive returns error",
57+
path: `HKEY_INVALID\Some\Path`,
58+
wantErr: true,
59+
errContains: "invalid registry key hive",
60+
},
61+
}
62+
63+
for _, tt := range tests {
64+
t.Run(tt.name, func(t *testing.T) {
65+
key, path, err := parseRegistryKeyPath(tt.path)
66+
if tt.wantErr {
67+
require.Error(t, err)
68+
assert.Contains(t, err.Error(), tt.errContains)
69+
return
70+
}
71+
require.NoError(t, err)
72+
assert.Equal(t, tt.wantKey, key)
73+
assert.Equal(t, tt.wantPath, path)
74+
})
75+
}
76+
}
77+
78+
func TestGetNativeRegistryKeyItems_Integration(t *testing.T) {
79+
// This key exists on every Windows installation
80+
items, err := GetNativeRegistryKeyItems(`HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion`)
81+
require.NoError(t, err)
82+
require.NotEmpty(t, items, "CurrentVersion should have registry values")
83+
84+
// Check that well-known values exist
85+
found := make(map[string]bool)
86+
for _, item := range items {
87+
found[item.Key] = true
88+
}
89+
assert.True(t, found["CurrentBuild"], "expected CurrentBuild value")
90+
assert.True(t, found["ProductName"], "expected ProductName value")
91+
}
92+
93+
func TestGetNativeRegistryKeyChildren_Integration(t *testing.T) {
94+
// This key exists on every Windows installation and has children
95+
children, err := GetNativeRegistryKeyChildren(`HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft`)
96+
require.NoError(t, err)
97+
require.NotEmpty(t, children, "HKLM\\SOFTWARE\\Microsoft should have subkeys")
98+
99+
// Check that at least "Windows NT" subkey exists
100+
found := false
101+
for _, child := range children {
102+
if child.Name == "Windows NT" {
103+
found = true
104+
break
105+
}
106+
}
107+
assert.True(t, found, "expected 'Windows NT' subkey under HKLM\\SOFTWARE\\Microsoft")
108+
}
109+
110+
func TestGetNativeRegistryKeyItems_NotFound(t *testing.T) {
111+
_, err := GetNativeRegistryKeyItems(`HKEY_LOCAL_MACHINE\SOFTWARE\NonExistentKey12345`)
112+
require.Error(t, err)
113+
}

0 commit comments

Comments
 (0)