This commit is contained in:
+34
-3
@@ -1,6 +1,6 @@
|
||||
"""Stand-in Spotify Web API: just enough of /api/token, .../player/devices,
|
||||
.../player and .../player/seek to test spotify.py against, the same way fakes.py stands in
|
||||
for real HEOS hardware."""
|
||||
.../player, .../player/pause and .../player/seek to test spotify.py against, the same way
|
||||
fakes.py stands in for real HEOS hardware."""
|
||||
|
||||
import json
|
||||
import threading
|
||||
@@ -23,8 +23,11 @@ class FakeSpotify(threading.Thread):
|
||||
self.tokens_issued = 0
|
||||
self.transfers = [] # every PUT /v1/me/player body
|
||||
self.seeks = [] # every PUT /v1/me/player/seek's position_ms
|
||||
self.pauses = 0 # how many PUT /v1/me/player/pause it took
|
||||
self.reject_refresh = False # simulate a revoked refresh token
|
||||
self.scope = "user-read-playback-state user-modify-playback-state"
|
||||
self.player = None # GET /v1/me/player's body; None is no session (204)
|
||||
self.forbid = None # a plain-text 403 body every API call answers with
|
||||
|
||||
fake = self
|
||||
|
||||
@@ -39,6 +42,19 @@ class FakeSpotify(threading.Thread):
|
||||
if payload is not None:
|
||||
self.wfile.write(json.dumps(payload).encode())
|
||||
|
||||
def _forbidden(self):
|
||||
"""The real one refuses an account the developer app has not
|
||||
listed with a bare sentence, not the usual JSON error object."""
|
||||
if fake.forbid is None:
|
||||
return False
|
||||
body = fake.forbid.encode()
|
||||
self.send_response(403)
|
||||
self.send_header("Content-Type", "text/plain")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
return True
|
||||
|
||||
def _authorized(self):
|
||||
header = self.headers.get("Authorization", "")
|
||||
return header == f"Bearer {fake.valid_token}" and fake.valid_token is not None
|
||||
@@ -51,11 +67,14 @@ class FakeSpotify(threading.Thread):
|
||||
return
|
||||
fake.tokens_issued += 1
|
||||
fake.valid_token = f"token-{fake.tokens_issued}"
|
||||
self._send(200, {"access_token": fake.valid_token, "expires_in": 3600})
|
||||
self._send(200, {"access_token": fake.valid_token, "expires_in": 3600,
|
||||
"scope": fake.scope})
|
||||
return
|
||||
self._send(404, {"error": {"message": "not found"}})
|
||||
|
||||
def do_PUT(self):
|
||||
if self._forbidden():
|
||||
return
|
||||
if self.path == "/v1/me/player":
|
||||
if not self._authorized():
|
||||
self._send(401, {"error": {"message": "The access token expired"}})
|
||||
@@ -65,6 +84,16 @@ class FakeSpotify(threading.Thread):
|
||||
self.send_response(204)
|
||||
self.end_headers()
|
||||
return
|
||||
if self.path == "/v1/me/player/pause":
|
||||
if not self._authorized():
|
||||
self._send(401, {"error": {"message": "The access token expired"}})
|
||||
return
|
||||
fake.pauses += 1
|
||||
if fake.player is not None:
|
||||
fake.player["is_playing"] = False
|
||||
self.send_response(204)
|
||||
self.end_headers()
|
||||
return
|
||||
if self.path.startswith("/v1/me/player/seek?"):
|
||||
if not self._authorized():
|
||||
self._send(401, {"error": {"message": "The access token expired"}})
|
||||
@@ -83,6 +112,8 @@ class FakeSpotify(threading.Thread):
|
||||
self._send(404, {"error": {"message": "not found"}})
|
||||
|
||||
def do_GET(self):
|
||||
if self._forbidden():
|
||||
return
|
||||
if self.path == "/v1/me/player/devices":
|
||||
if not self._authorized():
|
||||
self._send(401, {"error": {"message": "The access token expired"}})
|
||||
|
||||
Reference in New Issue
Block a user