Skip to content

Commit 94f04ff

Browse files
committed
Report OS id
1 parent 8a9e027 commit 94f04ff

9 files changed

Lines changed: 214 additions & 4 deletions

File tree

internal/api/submit/request.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ type System struct {
66

77
type OS struct {
88
Architecture string `json:"architecture"`
9+
Id string `json:"id"`
910
}
1011

1112
type Pacman struct {
@@ -30,6 +31,7 @@ type PackageManager interface {
3031
type SystemInfo interface {
3132
GetCpuArchitecture() (string, error)
3233
GetArchitecture() (string, error)
34+
GetOSId() (string, error)
3335
}
3436

3537
func CreateRequest(p PackageManager, s SystemInfo) (*Request, error) {
@@ -51,10 +53,15 @@ func CreateRequest(p PackageManager, s SystemInfo) (*Request, error) {
5153
return nil, err
5254
}
5355

56+
osId, err := s.GetOSId()
57+
if err != nil {
58+
return nil, err
59+
}
60+
5461
return &Request{
5562
Version: Version,
5663
System: System{Architecture: cpuArchitecture},
57-
OS: OS{Architecture: architecture},
64+
OS: OS{Architecture: architecture, Id: osId},
5865
Pacman: Pacman{Packages: packages, Mirror: mirror},
5966
}, nil
6067
}

internal/system/system_goos.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,7 @@ func (s *System) GetArchitecture() (string, error) {
1616
return runtime.GOARCH, nil
1717
}
1818
}
19+
20+
func (s *System) GetOSId() (string, error) {
21+
return runtime.GOOS, nil
22+
}

internal/system/system_linux.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package system
22

33
import (
44
"bytes"
5+
"os"
6+
"runtime"
7+
"strings"
58

69
"golang.org/x/sys/unix"
710
)
@@ -15,3 +18,42 @@ func (s *System) GetArchitecture() (string, error) {
1518

1619
return string(utsname.Machine[:bytes.IndexByte(utsname.Machine[:], 0)]), nil
1720
}
21+
22+
func (s *System) GetOSId() (string, error) {
23+
for _, path := range []string{"/etc/os-release", "/usr/lib/os-release"} {
24+
content, err := os.ReadFile(path)
25+
if err != nil {
26+
continue
27+
}
28+
if id := ParseOSId(content); id != "" {
29+
return id, nil
30+
}
31+
break
32+
}
33+
34+
return runtime.GOOS, nil
35+
}
36+
37+
func ParseOSId(content []byte) string {
38+
var id string
39+
40+
lines := strings.Split(string(content), "\n")
41+
const keyValueParts = 2
42+
for _, line := range lines {
43+
if trimmedLine := strings.TrimSpace(line); trimmedLine == "" || strings.HasPrefix(trimmedLine, "#") {
44+
continue
45+
}
46+
47+
parts := strings.SplitN(line, "=", keyValueParts)
48+
if len(parts) != keyValueParts {
49+
continue
50+
}
51+
52+
key := strings.TrimSpace(parts[0])
53+
if key == "ID" {
54+
id = strings.Trim(strings.TrimSpace(parts[1]), `"'`)
55+
}
56+
}
57+
58+
return id
59+
}

tests/integration/integration_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ func TestShowInformationToBeSent(t *testing.T) {
4343
if err != nil {
4444
t.Fatal(err)
4545
}
46+
osId, err := s.GetOSId()
47+
if err != nil {
48+
t.Fatal(err)
49+
}
4650

4751
output, err := pkgstats(t, []string{"submit", "--dump-json"}, WithPkgBlocklist([]string{"secret-*"}), WithMirrorBlocklist([]string{"secret.com"}))
4852
if err != nil {
@@ -61,6 +65,9 @@ func TestShowInformationToBeSent(t *testing.T) {
6165
if request.OS.Architecture != osArchitecture {
6266
t.Errorf("Expected OS architecture '%s', got %v", osArchitecture, request.OS.Architecture)
6367
}
68+
if request.OS.Id != osId {
69+
t.Errorf("Expected OS id '%s', got %v", osId, request.OS.Id)
70+
}
6471
if !strings.HasPrefix(request.Pacman.Mirror, "https://") {
6572
t.Errorf("Expected pacman mirror to start with 'https://', got %v", request.Pacman.Mirror)
6673
}

tests/integration/mock_server_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ func validateSubmitRequest(w http.ResponseWriter, request *submit.Request) {
108108
http.Error(w, err.Error(), http.StatusInternalServerError)
109109
return
110110
}
111+
osId, err := s.GetOSId()
112+
if err != nil {
113+
http.Error(w, err.Error(), http.StatusInternalServerError)
114+
return
115+
}
111116

112117
if request.Version != submit.Version {
113118
http.Error(w, fmt.Sprintf("Expected version %s, got %s", submit.Version, request.Version), http.StatusBadRequest)
@@ -117,6 +122,10 @@ func validateSubmitRequest(w http.ResponseWriter, request *submit.Request) {
117122
http.Error(w, fmt.Sprintf("Expected OS architecture %s, got %s", osArchitecture, request.OS.Architecture), http.StatusBadRequest)
118123
return
119124
}
125+
if request.OS.Id != osId {
126+
http.Error(w, fmt.Sprintf("Expected OS id %s, got %s", osId, request.OS.Id), http.StatusBadRequest)
127+
return
128+
}
120129
if request.System.Architecture != cpuArchitecture {
121130
http.Error(w, fmt.Sprintf("Expected CPU architecture %s, got %s", cpuArchitecture, request.System.Architecture), http.StatusBadRequest)
122131
return

tests/unit/internal/api/submit/client_test.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"io"
77
"net/http"
88
"net/http/httptest"
9+
"runtime"
910
"strings"
1011
"testing"
1112

@@ -32,7 +33,7 @@ func TestSendRequest(t *testing.T) {
3233
request := &submit.Request{
3334
Version: submit.Version,
3435
System: submit.System{Architecture: system.X86_64},
35-
OS: submit.OS{Architecture: system.I686},
36+
OS: submit.OS{Architecture: system.I686, Id: runtime.GOOS},
3637
Pacman: submit.Pacman{Packages: []string{"pacman", "linux"}, Mirror: mirror},
3738
}
3839
err := client.SendRequest(*request)
@@ -74,6 +75,9 @@ func validateRequest(t *testing.T, req *http.Request) {
7475
if request.OS.Architecture != system.I686 {
7576
t.Error("Invalid arch value")
7677
}
78+
if request.OS.Id != runtime.GOOS {
79+
t.Error("Invalid id value")
80+
}
7781
if request.Pacman.Mirror != mirror {
7882
t.Error("Invalid mirror value")
7983
}
@@ -101,7 +105,7 @@ func TestSendRequestFollowsRedirect(t *testing.T) {
101105
request := &submit.Request{
102106
Version: submit.Version,
103107
System: submit.System{Architecture: system.X86_64},
104-
OS: submit.OS{Architecture: system.I686},
108+
OS: submit.OS{Architecture: system.I686, Id: runtime.GOOS},
105109
Pacman: submit.Pacman{Packages: []string{"pacman", "linux"}, Mirror: mirror},
106110
}
107111
err := client.SendRequest(*request)
@@ -128,7 +132,7 @@ func TestReturnServerErrorOnFailure(t *testing.T) {
128132
request := &submit.Request{
129133
Version: submit.Version,
130134
System: submit.System{Architecture: system.X86_64},
131-
OS: submit.OS{Architecture: system.I686},
135+
OS: submit.OS{Architecture: system.I686, Id: runtime.GOOS},
132136
Pacman: submit.Pacman{Packages: []string{"pacman", "linux"}, Mirror: mirror},
133137
}
134138
err := client.SendRequest(*request)

tests/unit/internal/system/system_goos_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,13 @@ func TestGetArchitecture(t *testing.T) {
1818
t.Error(cpuArch)
1919
}
2020
}
21+
22+
func TestGetOSId(t *testing.T) {
23+
osId, err := system.NewSystem().GetOSId()
24+
if err != nil {
25+
t.Error(err)
26+
}
27+
if osId != runtime.GOOS {
28+
t.Error(osId)
29+
}
30+
}

tests/unit/internal/system/system_linux_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package system_test
22

33
import (
4+
"os"
45
"runtime"
56
"slices"
67
"strings"
@@ -33,3 +34,24 @@ func TestGetMachine(t *testing.T) {
3334
t.Error(arch)
3435
}
3536
}
37+
38+
func TestGetOSId(t *testing.T) {
39+
sys := system.NewSystem()
40+
osId, err := sys.GetOSId()
41+
if err != nil {
42+
t.Fatalf("unexpected error getting OSId: %v", err)
43+
}
44+
45+
_, err1 := os.Stat("/etc/os-release")
46+
_, err2 := os.Stat("/usr/lib/os-release")
47+
48+
if os.IsNotExist(err1) && os.IsNotExist(err2) {
49+
if osId != runtime.GOOS {
50+
t.Errorf("expected OSId %q, got %q", runtime.GOOS, osId)
51+
}
52+
} else {
53+
if osId == "" {
54+
t.Error("expected a non-empty OSId, but it was empty")
55+
}
56+
}
57+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package system_test
2+
3+
import (
4+
"testing"
5+
6+
"pkgstats-cli/internal/system"
7+
)
8+
9+
func TestParseOSId(t *testing.T) {
10+
testCases := []struct {
11+
name string
12+
content string
13+
expectedOSId string
14+
}{
15+
{
16+
name: "should return Id from simple content",
17+
content: `
18+
NAME="Test OS"
19+
VERSION="1.0"
20+
ID=testos
21+
ID_LIKE=anotheros
22+
`,
23+
expectedOSId: "testos",
24+
},
25+
{
26+
name: "should return Id from double-quoted syntax with whitespaces",
27+
content: `
28+
NAME="Test OS"
29+
VERSION="1.0"
30+
ID = "testos"
31+
ID_LIKE=anotheros
32+
`,
33+
expectedOSId: "testos",
34+
},
35+
{
36+
name: "should return Id with whitespaces",
37+
content: `
38+
NAME="Test OS"
39+
VERSION="1.0"
40+
ID = testos
41+
ID_LIKE=anotheros
42+
`,
43+
expectedOSId: "testos",
44+
},
45+
{
46+
name: "should return Id from single-quoted syntax",
47+
content: `
48+
NAME="Test OS"
49+
VERSION="1.0"
50+
ID='testos'
51+
ID_LIKE=anotheros
52+
`,
53+
expectedOSId: "testos",
54+
},
55+
{
56+
name: "should return the last Id when duplicates exist",
57+
content: `
58+
ID=firstid
59+
NAME="Test OS"
60+
ID=secondid
61+
VERSION="1.0"
62+
ID=lastid
63+
`,
64+
expectedOSId: "lastid",
65+
},
66+
{
67+
name: "should return empty string for empty content",
68+
content: "",
69+
expectedOSId: "",
70+
},
71+
{
72+
name: "should return empty string for content with no Id",
73+
content: `
74+
NAME="Test OS"
75+
VERSION="1.0"
76+
`,
77+
expectedOSId: "",
78+
},
79+
{
80+
name: "should ignore comments",
81+
content: `
82+
#ID=commented
83+
NAME="Test OS"
84+
ID=actual
85+
`,
86+
expectedOSId: "actual",
87+
},
88+
{
89+
name: "should handle Id with embedded equals",
90+
content: `
91+
ID=some=value
92+
`,
93+
expectedOSId: "some=value",
94+
},
95+
}
96+
97+
for _, tc := range testCases {
98+
t.Run(tc.name, func(t *testing.T) {
99+
osId := system.ParseOSId([]byte(tc.content))
100+
if osId != tc.expectedOSId {
101+
t.Errorf("expected OSId %q, got %q", tc.expectedOSId, osId)
102+
}
103+
})
104+
}
105+
}

0 commit comments

Comments
 (0)