diff --git a/pkg/service/handlers/handlers_marge.go b/pkg/service/handlers/handlers_marge.go index b65cd7b..b7d7471 100644 --- a/pkg/service/handlers/handlers_marge.go +++ b/pkg/service/handlers/handlers_marge.go @@ -460,13 +460,15 @@ func (s *Server) HandleMargeAddDevice(w http.ResponseWriter, r *http.Request) { return } - data, err := marge.AddDeviceToAccount(s.ds, account, body) + deviceID, data, err := marge.AddDeviceToAccount(s.ds, account, body) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.2+xml") + w.Header().Set("Location", s.serverURL+"/account/"+account+"/device/"+deviceID) + w.WriteHeader(http.StatusCreated) _, _ = w.Write(data) } diff --git a/pkg/service/handlers/handlers_marge_test.go b/pkg/service/handlers/handlers_marge_test.go index 95f7f65..ae8fd46 100644 --- a/pkg/service/handlers/handlers_marge_test.go +++ b/pkg/service/handlers/handlers_marge_test.go @@ -817,8 +817,13 @@ func TestMargeAddRemoveDevice(t *testing.T) { _ = res.Body.Close() - if res.StatusCode != http.StatusOK { - t.Errorf("AddDevice: Expected status OK, got %v", res.Status) + if res.StatusCode != http.StatusCreated { + t.Errorf("AddDevice: Expected status Created, got %v", res.Status) + } + + location := res.Header.Get("Location") + if !strings.Contains(location, "/account/"+account+"/device/NEWDEV") { + t.Errorf("AddDevice: Expected Location header containing /account/%s/device/NEWDEV, got %s", account, location) } deviceFile := filepath.Join(accountDir, "devices", "NEWDEV", "DeviceInfo.xml") diff --git a/pkg/service/marge/marge.go b/pkg/service/marge/marge.go index 8c32af6..d88cfd7 100644 --- a/pkg/service/marge/marge.go +++ b/pkg/service/marge/marge.go @@ -969,14 +969,14 @@ func formatRecentResponse(recentObj *models.ServiceRecent, matchingSrc *models.C } // AddDeviceToAccount adds a new device to the specified account. -func AddDeviceToAccount(ds *datastore.DataStore, account string, sourceXML []byte) ([]byte, error) { +func AddDeviceToAccount(ds *datastore.DataStore, account string, sourceXML []byte) (string, []byte, error) { var newDeviceElem struct { DeviceID string `xml:"deviceid,attr"` Name string `xml:"name"` MACAddress string `xml:"macaddress"` } if err := xml.Unmarshal(sourceXML, &newDeviceElem); err != nil { - return nil, err + return "", nil, err } info := &models.ServiceDeviceInfo{ @@ -987,7 +987,7 @@ func AddDeviceToAccount(ds *datastore.DataStore, account string, sourceXML []byt } if err := ds.SaveDeviceInfo(account, newDeviceElem.DeviceID, info); err != nil { - return nil, err + return "", nil, err } createdOn := FormatTime(time.Now()) @@ -1000,7 +1000,7 @@ func AddDeviceToAccount(ds *datastore.DataStore, account string, sourceXML []byt header := constants.XMLHeader - return append([]byte(header), []byte(res)...), nil + return newDeviceElem.DeviceID, append([]byte(header), []byte(res)...), nil } // RemoveDeviceFromAccount removes a device from the specified account. diff --git a/tests/integration/http-client/register_device.http b/tests/integration/http-client/register_device.http index 6336ebb..d344435 100644 --- a/tests/integration/http-client/register_device.http +++ b/tests/integration/http-client/register_device.http @@ -12,10 +12,27 @@ Authorization: Bearer {{token}} > {% client.test("Device registered successfully", function() { client.assert(response.status === 200 || response.status === 201, "Response status is not 200 or 201"); + client.assert(response.contentType.mimeType === "application/vnd.bose.streaming-v1.2+xml", "Response Content-Type should be application/vnd.bose.streaming-v1.2+xml"); + const doc = response.body; const device = doc.getElementsByTagName("device")[0]; client.assert(device !== undefined, "Response body should contain "); client.assert(device.getAttribute("deviceid") === client.variables.environment.get("deviceId"), "Response body should contain the deviceId"); + + const name = device.getElementsByTagName("name")[0]; + client.assert(name !== undefined, "Response body should contain "); + client.assert(name.textContent === client.variables.environment.get("deviceName"), "name should match requested name"); + + const createdOn = device.getElementsByTagName("createdOn")[0]; + client.assert(createdOn !== undefined, "Response body should contain "); + client.assert(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|(\+\d{2}:\d{2}))$/.test(createdOn.textContent), "createdOn should be a valid ISO8601 timestamp"); + + const updatedOn = device.getElementsByTagName("updatedOn")[0]; + client.assert(updatedOn !== undefined, "Response body should contain "); + client.assert(updatedOn.textContent === createdOn.textContent, "updatedOn should match createdOn for a new device"); + + const ipaddress = device.getElementsByTagName("ipaddress")[0]; + client.assert(ipaddress !== undefined, "Response body should contain "); }); %} @@ -32,7 +49,10 @@ Authorization: Bearer {{token}} > {% client.test("Device registered successfully (variant)", function() { - client.assert(response.status === 200 || response.status === 201, "Response status is not 200 or 201"); + client.assert(response.status === 200 || response.status === 201, "Response status should be 200 or 201"); + client.assert(response.contentType.mimeType === "application/vnd.bose.streaming-v1.2+xml", "Response Content-Type should be application/vnd.bose.streaming-v1.2+xml"); + client.assert(response.headers.valueOf("Location").includes("/account/" + client.variables.environment.get("accountId") + "/device/" + client.variables.environment.get("deviceId")), "Location header should point to the created device"); + const doc = response.body; const device = doc.getElementsByTagName("device")[0]; client.assert(device !== undefined, "Response body should contain "); @@ -40,7 +60,7 @@ Authorization: Bearer {{token}} const createdOn = device.getElementsByTagName("createdOn")[0]; client.assert(createdOn !== undefined, "Response body should contain "); - client.assert(createdOn.textContent.length > 0, "createdOn should not be empty"); + client.assert(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|(\+\d{2}:\d{2}))$/.test(createdOn.textContent), "createdOn should be a valid ISO8601 timestamp"); const name = device.getElementsByTagName("name")[0]; client.assert(name !== undefined, "Response body should contain ");