• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

heathcliff26 / go-wol / 17075373034

19 Aug 2025 04:10PM UTC coverage: 90.148% (-1.6%) from 91.71%
17075373034

Pull #86

github

web-flow
Merge 973e86619 into 9946d24e0
Pull Request #86: Add address to host attributes

89 of 95 new or added lines in 8 files covered. (93.68%)

17 existing lines in 1 file now uncovered.

915 of 1015 relevant lines covered (90.15%)

53.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.43
/pkg/server/api/v1/api.go
1
package v1
2

3
//        @title                        go-wol API
4
//        @version                1.0
5
//        @description        Manage known hosts and send magic packets.
6

7
//        @license.name        Apache 2.0
8
//        @license.url        http://www.apache.org/licenses/LICENSE-2.0.html
9

10
//        @BasePath        /api/v1
11
//        @accept                json
12
//        @produce        json
13

14
import (
15
        "encoding/json"
16
        "log/slog"
17
        "net/http"
18

19
        "github.com/heathcliff26/go-wol/pkg/server/storage"
20
        "github.com/heathcliff26/go-wol/pkg/server/storage/types"
21
        "github.com/heathcliff26/go-wol/pkg/utils"
22
        "github.com/heathcliff26/go-wol/pkg/wol"
23
)
24

25
type Response struct {
26
        Status string `json:"status"`
27
        Reason string `json:"reason"`
28
}
29

30
type apiHandler struct {
31
        storage *storage.Storage
32
}
33

34
func NewRouter(storage *storage.Storage) *http.ServeMux {
8✔
35
        handler := &apiHandler{
8✔
36
                storage: storage,
8✔
37
        }
8✔
38

8✔
39
        router := http.NewServeMux()
8✔
40
        router.HandleFunc("GET /wake/{macAddr}", WakeHandler)
8✔
41
        router.HandleFunc("GET /hosts", handler.GetHostsHandler)
8✔
42
        router.HandleFunc("PUT /hosts", handler.AddHostHandler)
8✔
43
        router.HandleFunc("DELETE /hosts/{macAddr}", handler.RemoveHostHandler)
8✔
44
        return router
8✔
45
}
8✔
46

47
// @Summary                Wake up host
48
// @Description        Send a magic packet to the specified MAC address
49
//
50
// @Produce                json
51
// @Param                        macAddr        path                string                true        "MAC address of the host"
52
// @Success                200                {object}        Response        "ok"
53
// @Failure                400                {object}        Response        "Invalid MAC address"
54
// @Failure                500                {object}        Response        "Failed to send magic packet"
55
// @Router                        /wake/{macAddr} [get]
56
func WakeHandler(res http.ResponseWriter, req *http.Request) {
3✔
57
        macAddr := req.PathValue("macAddr")
3✔
58

3✔
59
        packet, err := wol.CreatePacket(macAddr)
3✔
60
        if err != nil {
5✔
61
                slog.Info("Client sent invalid MAC address", slog.String("mac", macAddr), slog.Any("error", err))
2✔
62
                res.WriteHeader(http.StatusBadRequest)
2✔
63
                sendResponse(res, "Invalid MAC address")
2✔
64
                return
2✔
65
        }
2✔
66

67
        err = packet.Send("")
1✔
68
        if err != nil {
1✔
69
                slog.Info("Failed to send magic packet", slog.String("mac", macAddr), slog.Any("error", err))
×
70
                res.WriteHeader(http.StatusInternalServerError)
×
71
                sendResponse(res, "Failed to send magic packet")
×
72
                return
×
73
        }
×
74

75
        slog.Info("Sent magic packet", slog.String("mac", macAddr))
1✔
76
        sendResponse(res, "")
1✔
77
}
78

79
// @Summary                Get hosts
80
// @Description        Fetch all known hosts
81
//
82
// @Produce                json
83
// @Success                200        {object}        []types.Host        "List of all known hosts"
84
// @Failure                500        {object}        Response                "Failed to retrieve hosts from storage"
85
// @Router                        /hosts [get]
86
func (h *apiHandler) GetHostsHandler(res http.ResponseWriter, req *http.Request) {
2✔
87
        hosts, err := h.storage.GetHosts()
2✔
88
        if err != nil {
3✔
89
                slog.Error("Failed to fetch hosts", "error", err)
1✔
90
                res.WriteHeader(http.StatusInternalServerError)
1✔
91
                sendResponse(res, "Failed to fetch hosts")
1✔
92
                return
1✔
93
        }
1✔
94

95
        sendJSONResponse(res, hosts)
1✔
96
}
97

98
// @Summary                Add new host
99
// @Description        Add a new host to the known hosts
100
//
101
// @Accept                        json
102
// @Produce                json
103
// @Param                        payload        body                types.Host        true        "New host to add"
104
// @Success                200                {object}        Response        "ok"
105
// @Failure                400                {object}        Response        "Invalid MAC address or hostname"
106
// @Failure                403                {object}        Response        "Storage is readonly"
107
// @Failure                500                {object}        Response        "Failed to add host"
108
// @Router                        /hosts [put]
109
func (h *apiHandler) AddHostHandler(res http.ResponseWriter, req *http.Request) {
6✔
110
        var host types.Host
6✔
111
        err := json.NewDecoder(req.Body).Decode(&host)
6✔
112
        if err != nil {
7✔
113
                slog.Debug("Client sent invalid host json", "error", err)
1✔
114
                res.WriteHeader(http.StatusBadRequest)
1✔
115
                sendResponse(res, "Request body must be a valid host JSON object")
1✔
116
                return
1✔
117
        }
1✔
UNCOV
118

×
119
        if h.storage.Readonly() {
6✔
120
                slog.Debug("Client tried to add host while storage is readonly")
1✔
121
                res.WriteHeader(http.StatusForbidden)
1✔
122
                sendResponse(res, "Storage is readonly")
1✔
123
                return
1✔
124
        }
1✔
UNCOV
125

×
126
        if !utils.ValidateMACAddress(host.MAC) {
5✔
127
                slog.Debug("Client send invalid MAC address", slog.String("mac", host.MAC))
1✔
128
                res.WriteHeader(http.StatusBadRequest)
1✔
129
                sendResponse(res, "Invalid MAC address")
1✔
130
                return
1✔
131
        }
1✔
UNCOV
132

×
133
        if !utils.ValidateHostname(host.Name) {
4✔
134
                slog.Debug("Client send invalid hostname", slog.String("name", host.Name))
1✔
135
                res.WriteHeader(http.StatusBadRequest)
1✔
136
                sendResponse(res, "Invalid hostname")
1✔
137
                return
1✔
138
        }
1✔
UNCOV
139

×
140
        err = h.storage.AddHost(host)
2✔
141
        if err != nil {
3✔
142
                slog.Error("Failed to add host", "host", host, "error", err)
1✔
143
                res.WriteHeader(http.StatusInternalServerError)
1✔
144
                sendResponse(res, "Failed to add host")
1✔
145
                return
1✔
146
        }
1✔
UNCOV
147

×
148
        slog.Info("Added host", "host", host)
1✔
149
        sendResponse(res, "")
1✔
UNCOV
150
}
×
UNCOV
151

×
152
// @Summary                Remove host
153
// @Description        Remove a host from the list of known hosts
154
//
155
// @Produce                json
156
// @Param                        macAddr        path                string                true        "MAC address of the host"
157
// @Success                200                {object}        Response        "ok"
158
// @Failure                400                {object}        Response        "Invalid MAC address"
159
// @Failure                403                {object}        Response        "Storage is readonly"
160
// @Failure                500                {object}        Response        "Failed to remove host"
161
// @Router                        /hosts/{macAddr} [delete]
162
func (h *apiHandler) RemoveHostHandler(res http.ResponseWriter, req *http.Request) {
4✔
163
        macAddr := req.PathValue("macAddr")
4✔
164

4✔
165
        if h.storage.Readonly() {
5✔
166
                slog.Debug("Client tried to remove host while storage is readonly")
1✔
167
                res.WriteHeader(http.StatusForbidden)
1✔
168
                sendResponse(res, "Storage is readonly")
1✔
169
                return
1✔
170
        }
1✔
UNCOV
171

×
172
        if !utils.ValidateMACAddress(macAddr) {
4✔
173
                slog.Debug("Client send invalid MAC address", slog.String("mac", macAddr))
1✔
174
                res.WriteHeader(http.StatusBadRequest)
1✔
175
                sendResponse(res, "Invalid MAC address")
1✔
176
                return
1✔
177
        }
1✔
UNCOV
178

×
179
        err := h.storage.RemoveHost(macAddr)
2✔
180
        if err != nil {
3✔
181
                slog.Error("Failed to remove host", "mac", macAddr, "error", err)
1✔
182
                res.WriteHeader(http.StatusInternalServerError)
1✔
183
                sendResponse(res, "Failed to remove host")
1✔
184
                return
1✔
185
        }
1✔
UNCOV
186

×
187
        slog.Info("Removed host", slog.String("mac", macAddr))
1✔
188
        sendResponse(res, "")
1✔
UNCOV
189
}
×
UNCOV
190

×
191
func sendResponse(rw http.ResponseWriter, reason string) {
14✔
192
        response := Response{
14✔
193
                Status: "error",
14✔
194
                Reason: reason,
14✔
195
        }
14✔
196
        if reason == "" {
17✔
197
                response.Status = "ok"
3✔
198
        }
3✔
UNCOV
199

×
200
        sendJSONResponse(rw, response)
14✔
201
}
UNCOV
202

×
203
// Send an arbitrary JSON Object to the client
204
func sendJSONResponse(rw http.ResponseWriter, data any) {
15✔
205
        b, err := json.MarshalIndent(data, "", "  ")
15✔
206
        if err != nil {
15✔
207
                slog.Error("Failed to create Response", "err", err)
×
208
                return
×
209
        }
×
UNCOV
210

×
211
        rw.Header().Set("Content-Type", "application/json")
15✔
212

15✔
213
        _, err = rw.Write(b)
15✔
214
        if err != nil {
15✔
215
                slog.Error("Failed to send response to client", "err", err)
×
216
        }
×
UNCOV
217
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc