1+ /*
2+ *
3+ * Copyright (C) 2026 Tobias Schoene www.yaacc.de
4+ *
5+ * This program is free software; you can redistribute it and/or
6+ * modify it under the terms of the GNU General Public License
7+ * as published by the Free Software Foundation; either version 3
8+ * of the License, or (at your option) any later version.
9+ *
10+ * This program is distributed in the hope that it will be useful,
11+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
12+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+ * GNU General Public License for more details.
14+ *
15+ * You should have received a copy of the GNU General Public License
16+ * along with this program; if not, write to the Free Software
17+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18+ */
119package de .yaacc .upnp .server .http ;
220
21+ import static org .junit .Assert .assertEquals ;
22+ import static org .junit .Assert .assertFalse ;
23+ import static org .junit .Assert .assertNotNull ;
24+ import static org .junit .Assert .assertTrue ;
25+
26+ import org .apache .hc .core5 .http .ContentType ;
27+ import org .apache .hc .core5 .http .io .entity .ByteArrayEntity ;
28+ import org .apache .hc .core5 .http .message .BasicClassicHttpResponse ;
29+ import org .fourthline .cling .model .message .StreamResponseMessage ;
330import org .junit .Test ;
431
5- import static org . junit . Assert .* ;
32+ import java . nio . charset . StandardCharsets ;
633
734public class HttpRequestSenderTest {
835
@@ -18,4 +45,84 @@ public void testCreateRequestMessage() {
1845 HttpRequestSender httpRequestSender = new HttpRequestSender ();
1946 assertNotNull (httpRequestSender );
2047 }
48+
49+ /**
50+ * Happy path: an HTTP 200 response is mapped to a successful UpnpResponse
51+ * with the canonical "OK" status message.
52+ */
53+ @ Test
54+ public void testCreateResponse_200_mapsToOk () throws Exception {
55+ StreamResponseMessage msg = new HttpRequestSender ().createResponse (buildResponse (200 , "<root/>" ));
56+ assertNotNull (msg );
57+ assertEquals (200 , msg .getOperation ().getStatusCode ());
58+ assertEquals ("OK" , msg .getOperation ().getStatusMessage ());
59+ assertFalse (msg .getOperation ().isFailed ());
60+ }
61+
62+ /**
63+ * Regression for issue #232. A 401 (e.g. Sky Q DVR's authenticated UPnP
64+ * service) used to throw IllegalStateException out of createResponse and
65+ * crash the discovery worker. It must now produce a synthetic failed
66+ * UpnpResponse so the caller can skip the device.
67+ */
68+ @ Test
69+ public void testCreateResponse_401_doesNotThrow () throws Exception {
70+ StreamResponseMessage msg = new HttpRequestSender ().createResponse (buildResponse (401 , "Unauthorized" ));
71+ assertNotNull (msg );
72+ assertEquals (401 , msg .getOperation ().getStatusCode ());
73+ assertEquals ("Unauthorized" , msg .getOperation ().getStatusMessage ());
74+ assertTrue ("401 must report isFailed() so the discovery loop skips the device" ,
75+ msg .getOperation ().isFailed ());
76+ }
77+
78+ /**
79+ * Regression for issue #232. A 403 produce a synthetic failed
80+ * UpnpResponse so the caller can skip the device.
81+ */
82+ @ Test
83+ public void testCreateResponse_403_doesNotThrow () throws Exception {
84+ StreamResponseMessage msg = new HttpRequestSender ().createResponse (buildResponse (403 , "Forbidden" ));
85+ assertNotNull (msg );
86+ assertEquals (403 , msg .getOperation ().getStatusCode ());
87+ assertEquals ("Forbidden" , msg .getOperation ().getStatusMessage ());
88+ assertTrue ("403 must report isFailed() so the discovery loop skips the device" ,
89+ msg .getOperation ().isFailed ());
90+ }
91+
92+ /**
93+ * Regression for issue #219 / PR #221. 503 was added to the canonical
94+ * Status enum in that PR; verify it still maps to the canonical
95+ * "Service Unavailable" message rather than the synthetic fallback.
96+ */
97+ @ Test
98+ public void testCreateResponse_503_mapsToServiceUnavailable () throws Exception {
99+ StreamResponseMessage msg = new HttpRequestSender ().createResponse (buildResponse (503 , "" ));
100+ assertNotNull (msg );
101+ assertEquals (503 , msg .getOperation ().getStatusCode ());
102+ assertEquals ("Service Unavailable" , msg .getOperation ().getStatusMessage ());
103+ assertTrue (msg .getOperation ().isFailed ());
104+ }
105+
106+ /**
107+ * Any HTTP status not in UpnpResponse.Status (here: a deliberately
108+ * fictitious 418) must not throw and must produce a synthetic failed
109+ * response. This guards against future status codes the canonical enum
110+ * does not yet know about (issue #232 explicitly argues against the
111+ * whack-a-mole alternative of expanding the enum one code at a time).
112+ */
113+ @ Test
114+ public void testCreateResponse_418_unmappedDoesNotThrow () throws Exception {
115+ StreamResponseMessage msg = new HttpRequestSender ().createResponse (buildResponse (418 , "I'm a teapot" ));
116+ assertNotNull (msg );
117+ assertEquals (418 , msg .getOperation ().getStatusCode ());
118+ assertEquals ("HTTP 418" , msg .getOperation ().getStatusMessage ());
119+ assertTrue (msg .getOperation ().isFailed ());
120+ }
121+
122+ private static BasicClassicHttpResponse buildResponse (int statusCode , String body ) {
123+ BasicClassicHttpResponse response = new BasicClassicHttpResponse (statusCode );
124+ byte [] bytes = body == null ? new byte [0 ] : body .getBytes (StandardCharsets .UTF_8 );
125+ response .setEntity (new ByteArrayEntity (bytes , ContentType .TEXT_PLAIN ));
126+ return response ;
127+ }
21128}
0 commit comments