Coverage for tests/login/tests_login_handler.py: 100%

111 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-28 22:05 +0000

1import requests 

2 

3import responses 

4from flask_testing import TestCase 

5from pymacaroons import Macaroon 

6from webapp.app import create_app 

7 

8from unittest.mock import patch, MagicMock 

9 

10 

11class LoginHandlerTest(TestCase): 

12 def setUp(self): 

13 self.api_url = "https://dashboard.snapcraft.io/dev/api/acl/" 

14 self.endpoint_url = "/login" 

15 

16 def create_app(self): 

17 app = create_app(testing=True) 

18 app.secret_key = "secret_key" 

19 app.config["WTF_CSRF_METHODS"] = [] 

20 

21 return app 

22 

23 def test_redirect_user_logged_in(self): 

24 with self.client.session_transaction() as s: 

25 s["publisher"] = "openid" 

26 s["macaroon_root"] = "macaroon_root" 

27 s["macaroon_discharge"] = "macaroon_discharge" 

28 

29 response = self.client.get(self.endpoint_url) 

30 assert response.status_code == 302 

31 self.assertEqual("http://localhost/", response.location) 

32 

33 def test_redirect_user_logged_in_next_url(self): 

34 with self.client.session_transaction() as s: 

35 s["publisher"] = "openid" 

36 s["macaroon_root"] = "macaroon_root" 

37 s["macaroon_discharge"] = "macaroon_discharge" 

38 

39 response = self.client.get(self.endpoint_url + "?next=/test") 

40 assert response.status_code == 302 

41 self.assertEqual("/test", response.location) 

42 

43 @responses.activate 

44 def test_login_handler_redirect(self): 

45 m = Macaroon() 

46 m.add_third_party_caveat("login.ubuntu.com", "key", "id") 

47 

48 serialized_macaroon = m.serialize() 

49 

50 responses.add( 

51 responses.Response( 

52 method="POST", 

53 url=self.api_url, 

54 json={"macaroon": serialized_macaroon}, 

55 status=200, 

56 ) 

57 ) 

58 

59 response = self.client.get(self.endpoint_url) 

60 

61 assert len(responses.calls) == 1 

62 assert response.status_code == 302 

63 

64 @responses.activate 

65 def test_login_api_500(self): 

66 responses.add( 

67 responses.Response(method="POST", url=self.api_url, status=500) 

68 ) 

69 

70 response = self.client.get(self.endpoint_url) 

71 

72 assert len(responses.calls) == 1 

73 assert response.status_code == 502 

74 

75 @responses.activate 

76 def test_login_api_401(self): 

77 responses.add( 

78 responses.Response(method="POST", url=self.api_url, status=401) 

79 ) 

80 

81 response = self.client.get(self.endpoint_url) 

82 

83 assert len(responses.calls) == 1 

84 assert response.status_code == 302 

85 self.assertEqual("/logout", response.location) 

86 

87 @responses.activate 

88 def test_login_connection_error(self): 

89 responses.add( 

90 responses.Response( 

91 method="POST", 

92 url=self.api_url, 

93 body=requests.exceptions.ConnectionError(), 

94 status=500, 

95 ) 

96 ) 

97 

98 response = self.client.get(self.endpoint_url) 

99 

100 assert response.status_code == 502 

101 

102 

103class AfterLoginHandlerTest(TestCase): 

104 def create_app(self): 

105 app = create_app(testing=True) 

106 

107 # set up a fake route for testing the after_login function 

108 # since it is decorated with @open_id.after_login 

109 @app.route("/_test_after_login") 

110 def _test_after_login(): 

111 from webapp.login.views import after_login 

112 

113 return after_login(self.mock_resp) 

114 

115 return app 

116 

117 # creates a mocked responses for the get_account function 

118 # and the login response passed to after_login 

119 def prepare_mock_response( 

120 self, mock_get_account, email="test@test.com", groups=[] 

121 ): 

122 self.mock_resp = MagicMock() 

123 self.mock_resp.nickname = "test" 

124 self.mock_resp.identity_url = "https://login.ubuntu.com/test" 

125 self.mock_resp.fullname = "Test" 

126 self.mock_resp.image = "test.png" 

127 self.mock_resp.email = email 

128 self.mock_resp.extensions = { 

129 "macaroon": MagicMock(discharge="some-discharge"), 

130 "lp": MagicMock(is_member=groups), 

131 } 

132 

133 mock_get_account.return_value = { 

134 "username": self.mock_resp.nickname, 

135 "displayname": self.mock_resp.fullname, 

136 "email": email, 

137 "stores": [], 

138 } 

139 

140 @patch("webapp.login.views.ENVIRONMENT", "staging") 

141 @patch("webapp.login.views.dashboard.get_stores", return_value=[]) 

142 @patch("webapp.login.views.logic.get_stores", return_value=[]) 

143 @patch( 

144 "webapp.login.views.dashboard.get_validation_sets", return_value=None 

145 ) 

146 @patch("webapp.login.views.dashboard.get_account") 

147 def test_is_canonical_true_if_email_ends_with_canonical_on_staging( 

148 self, 

149 mock_get_account, 

150 *_, 

151 ): 

152 # on test environments, we treat publisher's account as "canonical" 

153 # if their email is (at)canonical email 

154 self.prepare_mock_response( 

155 mock_get_account, email="test@canonical.com", groups=[] 

156 ) 

157 

158 self.client.get("/_test_after_login") 

159 

160 with self.client.session_transaction() as s: 

161 publisher = s.get("publisher") 

162 assert publisher is not None 

163 assert publisher["is_canonical"] is True 

164 

165 @patch("webapp.login.views.ENVIRONMENT", "production") 

166 @patch("webapp.login.views.dashboard.get_stores", return_value=[]) 

167 @patch("webapp.login.views.logic.get_stores", return_value=[]) 

168 @patch( 

169 "webapp.login.views.dashboard.get_validation_sets", return_value=None 

170 ) 

171 @patch("webapp.login.views.dashboard.get_account") 

172 def test_is_canonical_true_if_member_of_team_on_production( 

173 self, 

174 mock_get_account, 

175 *_, 

176 ): 

177 # on production, we treat publisher's account as "canonical" 

178 # if they are a member of the canonical team 

179 self.prepare_mock_response(mock_get_account, groups=["canonical"]) 

180 

181 self.client.get("/_test_after_login") 

182 

183 with self.client.session_transaction() as s: 

184 publisher = s.get("publisher") 

185 assert publisher is not None 

186 assert publisher["is_canonical"] is True 

187 

188 @patch("webapp.login.views.ENVIRONMENT", "production") 

189 @patch("webapp.login.views.dashboard.get_stores", return_value=[]) 

190 @patch("webapp.login.views.logic.get_stores", return_value=[]) 

191 @patch( 

192 "webapp.login.views.dashboard.get_validation_sets", return_value=None 

193 ) 

194 @patch("webapp.login.views.dashboard.get_account") 

195 def test_is_canonical_false_if_not_member_of_team_on_production( 

196 self, 

197 mock_get_account, 

198 *_, 

199 ): 

200 # on production, we treat publisher's account as "canonical" 

201 # only if they are a member of the canonical team, not based on email 

202 self.prepare_mock_response( 

203 mock_get_account, email="test@canonical.com", groups=[] 

204 ) 

205 

206 self.client.get("/_test_after_login") 

207 

208 with self.client.session_transaction() as s: 

209 publisher = s.get("publisher") 

210 assert publisher is not None 

211 assert publisher["is_canonical"] is False