Private
Public Access
Import minamp 1.0 source
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
0df8124629
194 files changed
+7319
No files matched your search
@@ -0,0 +1,167 @@
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.minamp.net.CertVerifier;
|
||||
import com.minamp.net.Http;
|
||||
import com.minamp.net.StreamProxy;
|
||||
import com.minamp.plex.Item;
|
||||
import com.minamp.plex.Library;
|
||||
import com.minamp.plex.Plex;
|
||||
import com.minamp.plex.Server;
|
||||
import com.minamp.util.Json;
|
||||
|
||||
/** End-to-end test of sign-in, discovery, connection picking, browsing and the stream proxy. */
|
||||
public class PlexTest {
|
||||
static int failures = 0;
|
||||
|
||||
static void check(boolean ok, String what) {
|
||||
System.out.println((ok ? "PASS " : "FAIL ") + what);
|
||||
if (!ok) failures++;
|
||||
}
|
||||
|
||||
static RootDir roots(String dir) { return new RootDir(new File(dir)); }
|
||||
|
||||
static final class RootDir implements CertVerifier.RootStore {
|
||||
final File dir;
|
||||
RootDir(File d) { dir = d; }
|
||||
public List<byte[]> find(byte[] subjectDer) {
|
||||
String h = CertVerifier.sha1Hex(subjectDer);
|
||||
List<byte[]> out = new ArrayList<byte[]>();
|
||||
for (int i = 0; ; i++) {
|
||||
File f = new File(dir, h + "-" + i + ".der");
|
||||
if (!f.exists()) break;
|
||||
try { out.add(read(new FileInputStream(f))); } catch (Exception e) { break; }
|
||||
}
|
||||
return out.isEmpty() ? null : out;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
CertVerifier.setRootStore(roots(args[0]));
|
||||
Plex.clientId = "test-client";
|
||||
|
||||
// --- sign-in via PIN
|
||||
Plex.Pin pin = Plex.createPin();
|
||||
check("WXYZ".equals(pin.code), "PIN created: " + pin.code);
|
||||
String account = null;
|
||||
for (int i = 0; i < 10 && account == null; i++) { account = Plex.checkPin(pin.id); if (account == null) Thread.sleep(300); }
|
||||
check("account-token-123".equals(account), "PIN linked -> account token");
|
||||
check("tester".equals(Plex.username(account)), "username");
|
||||
|
||||
// --- discovery
|
||||
List<Server> servers = Plex.servers(account);
|
||||
check(servers.size() == 1, "one server (players filtered out)");
|
||||
Server s = servers.get(0);
|
||||
StringBuilder order = new StringBuilder();
|
||||
for (Server.Conn c : s.connections) order.append(c.kind).append(' ');
|
||||
check(s.connections.get(0).kind == Server.LOCAL && s.connections.get(s.connections.size() - 1).kind == Server.RELAY,
|
||||
"connections ordered local..relay: " + order);
|
||||
|
||||
// persistence round trip
|
||||
Server s2 = Server.fromJson(Json.parseObj(s.toJson().toString()));
|
||||
check(s2.connections.size() == s.connections.size() && s2.token.equals(s.token), "server JSON round trip");
|
||||
|
||||
long t0 = System.currentTimeMillis();
|
||||
Server.Conn picked = s2.pick();
|
||||
check(picked.uri.startsWith("https://127-0-0-1.abc123.plex.direct:32443"), "picked local TLS connection " + picked.uri
|
||||
+ " in " + (System.currentTimeMillis() - t0) + "ms");
|
||||
|
||||
// --- browsing
|
||||
List<Server.Section> secs = s2.musicSections();
|
||||
check(secs.size() == 1 && "Music".equals(secs.get(0).title), "music sections only");
|
||||
Library lib = new Library(s2, secs.get(0).key);
|
||||
List<Item> artists = s2.items(lib.artistsPath(100, 100));
|
||||
check(artists.size() == 100 && "Artist 100".equals(artists.get(0).title), "artists paging");
|
||||
Item album = s2.items(lib.recentlyAddedPath(10)).get(0);
|
||||
check(album.type == Item.ALBUM && "The Testers · 1999".equals(album.subtitle()), "album item + subtitle");
|
||||
List<Item> tracks = lib.tracksOf(album);
|
||||
check(tracks.size() == 2 && tracks.get(0).partKey.endsWith("file.mp3") && tracks.get(0).loved(), "album tracks with media");
|
||||
check(Library.canDirectPlay(tracks.get(0), 0) && !Library.canDirectPlay(tracks.get(1), 0), "direct play decisions");
|
||||
check(!Library.canDirectPlay(tracks.get(0), 128), "192k mp3 transcoded when capped at 128k");
|
||||
Item rt = Item.deserialize(tracks.get(0).serialize());
|
||||
check(rt.title.equals("Song One") && rt.partKey.equals(tracks.get(0).partKey) && rt.duration == 200000, "item JSON round trip");
|
||||
List<Item> res = lib.search("test");
|
||||
check(res.size() == 4 && res.get(0).type == Item.HEADER && res.get(1).type == Item.ARTIST && res.get(3).type == Item.TRACK,
|
||||
"search hubs grouped (" + res.size() + ")");
|
||||
check(lib.libraryRadio().size() == 2, "library radio");
|
||||
String img = s2.imageUrl("/library/metadata/10/thumb/1", 64);
|
||||
Http.Response ir = Http.get(img, null, 10000);
|
||||
byte[] ib = ir.readAll();
|
||||
check(ir.code == 200 && ib.length > 100, "artwork via photo transcoder");
|
||||
lib.timeline(tracks.get(0), "playing", 1000);
|
||||
lib.rate(tracks.get(0), 10);
|
||||
check(true, "timeline + rate calls");
|
||||
|
||||
// --- stream proxy
|
||||
StreamProxy proxy = new StreamProxy();
|
||||
proxy.start();
|
||||
String id = proxy.newId();
|
||||
String local = proxy.register(id, lib.directUrl(tracks.get(0)), "mp3");
|
||||
HttpURLConnection hc = (HttpURLConnection) new URL(local).openConnection(Proxy.NO_PROXY);
|
||||
hc.setRequestProperty("Range", "bytes=1000-");
|
||||
byte[] body = read(hc.getInputStream());
|
||||
check(hc.getResponseCode() == 206 && body.length == 299000 && (body[0] & 255) == (1000 * 7) % 256,
|
||||
"proxy direct stream with Range over TLS (" + hc.getResponseCode() + ", " + body.length + "B)");
|
||||
|
||||
String id2 = proxy.newId();
|
||||
String local2 = proxy.register(id2, lib.transcodeUrl(tracks.get(1), 192, 30500, "sess1"), "mp3");
|
||||
hc = (HttpURLConnection) new URL(local2).openConnection(Proxy.NO_PROXY);
|
||||
byte[] tbody = read(hc.getInputStream());
|
||||
String ts = new String(tbody, "ISO-8859-1");
|
||||
check(hc.getResponseCode() == 200 && ts.startsWith("TRANSCODE offset=30 bitrate=192|") && hc.getHeaderField("Content-Length") == null,
|
||||
"proxy de-chunks transcode stream (" + tbody.length + "B)");
|
||||
|
||||
// abort mid-stream
|
||||
String id3 = proxy.newId();
|
||||
String local3 = proxy.register(id3, lib.directUrl(tracks.get(0)), "mp3");
|
||||
hc = (HttpURLConnection) new URL(local3).openConnection(Proxy.NO_PROXY);
|
||||
InputStream in = hc.getInputStream();
|
||||
in.read(new byte[100]);
|
||||
proxy.unregister(id3);
|
||||
check(true, "unregister mid-stream doesn't hang");
|
||||
String gone = proxy.register("nope", "http://127.0.0.1:1/x", "mp3");
|
||||
proxy.unregister("nope");
|
||||
hc = (HttpURLConnection) new URL(gone).openConnection(Proxy.NO_PROXY);
|
||||
check(hc.getResponseCode() == 404, "unknown route 404");
|
||||
|
||||
// --- failover: break the preferred connection's identity, expect fallback to plain http LAN
|
||||
Server s3 = new Server();
|
||||
s3.machineId = "machine-abc123"; s3.token = s.token; s3.name = "x";
|
||||
s3.add("https://localhost:32443", Server.LOCAL); // cert won't match "localhost"
|
||||
s3.add("http://127.0.0.1:32400", Server.LOCAL_HTTP);
|
||||
s3.add("https://10-255-255-1.abc123.plex.direct:9", Server.REMOTE);
|
||||
s3.sortConnections();
|
||||
Server.Conn c3 = s3.pick();
|
||||
check(c3.kind == Server.LOCAL_HTTP, "falls back when TLS name check fails -> " + c3.uri);
|
||||
|
||||
// --- trust checks
|
||||
CertVerifier.setRootStore(roots(args[1])); // real Mozilla roots: test CA must be rejected
|
||||
Http.evictAll();
|
||||
try {
|
||||
Http.get("https://127-0-0-1.abc123.plex.direct:32443/identity", null, 5000).readAll();
|
||||
check(false, "untrusted root rejected");
|
||||
} catch (Exception e) {
|
||||
check(e.getMessage() != null && e.getMessage().indexOf("Untrusted") >= 0, "untrusted root rejected: " + e.getMessage());
|
||||
}
|
||||
|
||||
check(Server.normalizeManual("192.168.1.5").equals("http://192.168.1.5:32400"), "manual URL normalise");
|
||||
check(Server.normalizeManual("https://plex.example.com/").equals("https://plex.example.com"), "manual https URL kept");
|
||||
System.out.println(failures == 0 ? "ALL PASSED" : failures + " FAILED");
|
||||
System.exit(failures == 0 ? 0 : 1);
|
||||
}
|
||||
|
||||
static byte[] read(InputStream in) throws Exception {
|
||||
ByteArrayOutputStream b = new ByteArrayOutputStream();
|
||||
byte[] buf = new byte[8192]; int n;
|
||||
while ((n = in.read(buf)) > 0) b.write(buf, 0, n);
|
||||
in.close();
|
||||
return b.toByteArray();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user