Skip to content

Commit

Permalink
Add test for error handling in episodes api (#8)
Browse files Browse the repository at this point in the history
* add test for quote error handling in episodes api

* add missing environment variables

* pass on all signingkeys to validator

* remove redundant logging
  • Loading branch information
steinsiv authored Jan 2, 2024
1 parent 7d60269 commit 70dbc8b
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import requests
from fastapi import HTTPException
from requests import get
from typing import List
from data.models import Episode
from data.got_demo_data import episodes
Expand All @@ -15,10 +15,13 @@ def get_random_quote(obo_token: str):
config = get_settings()
quote_endpoint = f"{ config.quotes_api_url }api/quote"
quote_headers = {"Authorization": f"Bearer {obo_token}"}
logger.warning(f"{quote_endpoint = }")
quote = requests.get(url= quote_endpoint, headers = quote_headers)
logger.info(f"Got a quote: {quote} {repr(quote)}")
return quote.json()
logger.info(f"{quote_endpoint = }")
try:
quote = get(url= quote_endpoint, headers = quote_headers).json()
logger.info(f"Got a quote: {quote} {repr(quote)}")
except Exception:
quote = {"title": "Quote error"}
return quote

def get_episode(episode_id: str) -> Episode:
episode = next((ep for ep in episodes if ep['id'] == episode_id), None)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import pytest
from controller.episodes_controller import get_all_episodes, get_episode, add_episode, update_episode, delete_episode
from unittest.mock import Mock
from controller.episodes_controller import get_all_episodes, get_episode, add_episode, update_episode, delete_episode, get_random_quote
from data.got_demo_data import episodes
from data.models import Episode

@pytest.fixture
def patchenv(monkeypatch):
monkeypatch.setenv('QUOTES_API_URL', 'https://test_quotes_api.url')
monkeypatch.setenv('TENANT_ID', '123')
monkeypatch.setenv('CLIENT_ID', '123')
monkeypatch.setenv('CLIENT_SECRET', '123')
monkeypatch.setenv('EPISODES_API_URI', 'api://123')
monkeypatch.setenv('QUOTES_API_URI', 'api://123')
test_episodes = [
{"id": "1", "title": 'Winter is coming', "season": 1},
{"id": "2", "title": 'The Kingsroad', "season": 1},
Expand All @@ -17,8 +24,14 @@ def patchenv(monkeypatch):
{"id": "9", "title": 'Bealor', "season": 1},
{"id": "10", "title": 'Fire and Blood', "season": 1},
]
monkeypatch.setattr("data.got_demo_data.episodes", test_episodes)
monkeypatch.setattr("controller.episodes_controller.episodes", test_episodes)

def mock_requests_get(*args, **kwargs):
mock_response = Mock()
mock_response.json.side_effect = Exception("Triggered exception")
return mock_response
monkeypatch.setattr("controller.episodes_controller.get", mock_requests_get)

yield monkeypatch

def test_get_all_episodes(patchenv):
Expand Down Expand Up @@ -56,3 +69,6 @@ def test_delete_episode(patchenv):
with pytest.raises(Exception):
get_episode(episode_id)

def test_get_random_quote(patchenv):
quote = get_random_quote("test_obo_token")
assert quote == {"title": "Quote error"}
5 changes: 3 additions & 2 deletions ex-11/got-episodes-api-python/tests/core/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def patchenv(monkeypatch):
monkeypatch.setenv('QUOTES_API_URI', 'test_quotes_api_uri')
monkeypatch.setenv('PORT', '7777')
monkeypatch.setenv('HOST', 'test_host')

yield monkeypatch

def test_valid_app_settings(patchenv):
Expand All @@ -35,7 +35,8 @@ def test_valid_app_settings(patchenv):
assert config.api_audience == f"api://{config.episodes_api_uri}"
assert config.issuer == HttpUrl(f"https://sts.windows.net/{config.tenant_id}/")

def test_missing_environment_variables():
def test_missing_environment_variables(patchenv):
patchenv.delenv('TENANT_ID')
with pytest.raises(KeyError):
get_settings()

Expand Down
17 changes: 6 additions & 11 deletions ex-11/got-quote-api-dotnet/src/utils/TokenValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,49 +43,44 @@ public bool IsValidToken(string token, TokenValidationParameters validationParam
{
// Check issuer
if (jwtToken.Issuer != validationParameters.ValidIssuer) {
_logger.LogError("Issuer is invalid");
throw new SecurityTokenInvalidIssuerException("Issuer is invalid");
};

// Check audience
if (jwtToken.Audiences.All(a => a != validationParameters.ValidAudience)) {
_logger.LogError("Audience is invalid");
throw new SecurityTokenInvalidAudienceException("Audience is invalid");
};

// Check signature and validate
var signingKey = validationParameters.IssuerSigningKeys.FirstOrDefault();
if (signingKey == null) {
_logger.LogError("Signing key is invalid");
throw new SecurityTokenInvalidSigningKeyException("Signing key is invalid");
if (!validationParameters.IssuerSigningKeys.Any()) {
throw new SecurityTokenInvalidSigningKeyException("No signing keys!");
};
var validationParametersWithSigningKey = new TokenValidationParameters
{
IssuerSigningKey = signingKey,
IssuerSigningKeys = validationParameters.IssuerSigningKeys,
ValidateIssuerSigningKey = true,
ValidateAudience = false,
ValidateIssuer = false,
ValidateLifetime = false
};
_ = tokenHandler.ValidateToken(token, validationParametersWithSigningKey, out _);

// Check valid timeframe
// Check valid timeframes
if (jwtToken.ValidFrom > DateTime.UtcNow || jwtToken.ValidTo < DateTime.UtcNow) {
_logger.LogError("Token is not valid in timeframe");
throw new SecurityTokenInvalidLifetimeException("Token is not valid in timeframe");
};

// Check scope
if (jwtToken.Claims.All(c => c.Type != "scp" || !c.Value.Split(' ').Any(s => s == "Quote.Read"))) {
_logger.LogError("Token does not contain correct scope");
throw new SecurityTokenInvalidLifetimeException("Token does not contain correct scope");
};

// All checks passed
return true;
}
catch
catch (Exception e)
{
_logger.LogError("Invalid token: {e.Message}", e.Message);
return false;
}
}
Expand Down

0 comments on commit 70dbc8b

Please sign in to comment.