saruberoz' site
Jekyll
2014-08-03T23:43:25+00:00
http://saruberoz.github.io/
Wilson Sumanang
http://saruberoz.github.io/
wilson.sumanang@yahoo.com
http://saruberoz.github.io/flask-session-cookie-decoder-slash-encoder
2014-08-03 23:14:09 +0000T00:00:00-00:00
2014-08-03T00:00:00+00:00
Wilson Sumanang
http://saruberoz.github.io
wilson.sumanang@yahoo.com
<p>What is a good way to test a flask web application routes/page that requires a user to login first? In my opinion there is two ways of doing it.</p>
<ol>
<li>do a <code>setup</code> and <code>teardown</code> where you login and logout after each test</li>
<li>Inject <code>session cookie</code> for all request (This will help speed up the test suite you have, because you don’t have to set the login/logout at the beginning and the end of test)</li>
</ol>
<p>Below is a simple python script to deal with Flask session cookie</p>
<p>What you will need the:</p>
<ol>
<li>flask app secret key (for encoding)</li>
<li>the session cookie structure/data for (decoding/encoding)</li>
</ol>
<p>How to use the python script:</p>
<ol>
<li>Use the <code>session_cookie_decoder</code> to get session cookie data/structure
<code>python session_cookie_manager.py <decode> <session_cookie_value></code></li>
<li>Use the <code>session_cookie_encoder</code> to setup a stub/mock <code>session cookie data</code>
<code>python session_cookie_manager.py <encode> <secret_key> <session_cookie_structure></code></li>
</ol>
<p><code>session_cookie_manager.py</code></p>
<div class="highlight"><pre><code class="language-python" data-lang="python"><span class="sd">""" Flask Session Cookie Decoder/Encoder """</span>
<span class="n">__author__</span> <span class="o">=</span> <span class="s">'Wilson Sumanang'</span>
<span class="c"># standard imports</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">zlib</span>
<span class="kn">from</span> <span class="nn">itsdangerous</span> <span class="kn">import</span> <span class="n">base64_decode</span>
<span class="c"># external Imports</span>
<span class="kn">from</span> <span class="nn">flask.sessions</span> <span class="kn">import</span> <span class="n">SecureCookieSessionInterface</span>
<span class="k">class</span> <span class="nc">MockApp</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">secret_key</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">secret_key</span> <span class="o">=</span> <span class="n">secret_key</span>
<span class="k">def</span> <span class="nf">session_cookie_encoder</span><span class="p">(</span><span class="n">secret_key</span><span class="p">,</span> <span class="n">session_cookie_structure</span><span class="p">):</span>
<span class="sd">""" Encode a Flask session cookie</span>
<span class="sd"> Example:</span>
<span class="sd"> cookie_structure = dict(</span>
<span class="sd"> gplus_id = 1285135705050360459231,</span>
<span class="sd"> email = john.doe@gmail.com,</span>
<span class="sd"> user_info = dict(</span>
<span class="sd"> full_name = john doe,</span>
<span class="sd"> )</span>
<span class="sd"> )</span>
<span class="sd"> session_cookie_encoder(b'development key', cookie_structure)</span>
<span class="sd"> Args:</span>
<span class="sd"> secret_key (string): Flask App secret key</span>
<span class="sd"> session_cookie_structure (dict): Flask session cookie structure</span>
<span class="sd"> Return:</span>
<span class="sd"> value (string): Flask session cookie</span>
<span class="sd"> """</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">app</span> <span class="o">=</span> <span class="n">MockApp</span><span class="p">(</span><span class="n">secret_key</span><span class="p">)</span>
<span class="n">session_cookie_structure</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
<span class="n">si</span> <span class="o">=</span> <span class="n">SecureCookieSessionInterface</span><span class="p">()</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">si</span><span class="o">.</span><span class="n">get_signing_serializer</span><span class="p">(</span><span class="n">app</span><span class="p">)</span>
<span class="k">return</span> <span class="n">s</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">session_cookie_structure</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="k">return</span> <span class="s">"[Encoding error]{}"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">session_cookie_decoder</span><span class="p">(</span><span class="n">session_cookie_value</span><span class="p">):</span>
<span class="sd">""" Decode a Flask cookie</span>
<span class="sd"> Args:</span>
<span class="sd"> session_cookie_value (string): Flask session cookie to decode</span>
<span class="sd"> """</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">compressed</span> <span class="o">=</span> <span class="bp">False</span>
<span class="n">payload</span> <span class="o">=</span> <span class="n">session_cookie_values</span>
<span class="k">if</span> <span class="n">payload</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="n">b</span><span class="s">'.'</span><span class="p">):</span>
<span class="n">compressed</span> <span class="o">=</span> <span class="bp">True</span>
<span class="n">payload</span> <span class="o">=</span> <span class="n">payload</span><span class="p">[</span><span class="mi">1</span><span class="p">:]</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">payload</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s">"."</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">base64_decode</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="k">if</span> <span class="n">compressed</span><span class="p">:</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">zlib</span><span class="o">.</span><span class="n">decompress</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="k">return</span> <span class="n">data</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="k">return</span> <span class="s">"[Decoding error]{}"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span>
<span class="n">encoder_guide</span> <span class="o">=</span> <span class="s">"Usage: session_cookie_manager.py <encode> <secret_key> <session_cookie_structure>"</span>
<span class="n">decoder_guide</span> <span class="o">=</span> <span class="s">"Usage: session_cookie_manager.py <decode> <session_cookie_value>"</span>
<span class="k">print</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span><span class="p">:</span>
<span class="n">status</span><span class="p">,</span> <span class="n">session_cookie_value</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">:]</span>
<span class="k">if</span> <span class="n">status</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span> <span class="o">==</span> <span class="s">'decode'</span><span class="p">:</span>
<span class="k">print</span> <span class="n">session_cookie_decoder</span><span class="p">(</span><span class="n">session_cookie_value</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">print</span> <span class="n">decoder_guide</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="k">elif</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">==</span> <span class="mi">4</span><span class="p">:</span>
<span class="n">status</span><span class="p">,</span> <span class="n">secret_key</span><span class="p">,</span> <span class="n">session_cookie_structure</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">:]</span>
<span class="k">if</span> <span class="n">status</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span> <span class="o">==</span> <span class="s">'encode'</span><span class="p">:</span>
<span class="k">print</span> <span class="n">session_cookie_encoder</span><span class="p">(</span><span class="n">secret_key</span><span class="p">,</span> <span class="n">session_cookie_structure</span><span class="p">)</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">print</span> <span class="n">encoder_guide</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">print</span> <span class="n">decoder_guide</span>
<span class="k">print</span> <span class="n">encoder_guide</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span></code></pre></div>
<p>By default Flask uses URL-safe signed serializer called itsdangerous to encoded its client-side session cookie. A flask app uses a secret key to sign the session cookie so that the client can’t modify it.<sup id="fnref:1"><a href="#fn:1" class="footnote">1</a></sup></p>
<div class="footnotes">
<ol>
<li id="fn:1">
<p><a href="https://www.kirsle.net/wizards/flask-session.py">https://www.kirsle.net/wizards/flask-session.py</a> <a href="#fnref:1" class="reversefootnote">↩</a></p>
</li>
</ol>
</div>
<p><a href="http://saruberoz.github.io/flask-session-cookie-decoder-slash-encoder/">Flask session cookie manager</a> was originally published by Wilson Sumanang at <a href="http://saruberoz.github.io">saruberoz' site</a> on August 03, 2014.</p>
http://saruberoz.github.io/unit-testing-flask-application
2014-07-12 06:43:30 +0000T00:00:00-00:00
2014-07-11T00:00:00+00:00
Wilson Sumanang
http://saruberoz.github.io
wilson.sumanang@yahoo.com
<h1 id="how-to-unit-testing-python-flask-app">How to unit-testing Python Flask App</h1>
<p>Lets say you have some production code which you want to test, you might wonder what is a good way to test it.</p>
<p>Here is some example how I have been doing it, hopefully it will be useful (ps. This is just one of many ways to do it)</p>
<p>Say you have some REST API routes</p>
<p><code>main.py</code></p>
<div class="highlight"><pre><code class="language-python" data-lang="python"><span class="nd">@app.route</span><span class="p">(</span><span class="s">'/environment'</span><span class="p">,</span> <span class="n">method</span><span class="o">=</span><span class="p">[</span><span class="s">'GET'</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">get_current_environment</span><span class="p">():</span>
<span class="sd">''' Get current application environment '''</span>
<span class="k">return</span> <span class="n">current_app</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s">'environment'</span><span class="p">)</span>
<span class="nd">@app.route</span><span class="p">(</span><span class="s">'/environment/<type>'</span><span class="p">,</span> <span class="n">method</span><span class="o">=</span><span class="p">[</span><span class="s">'POST'</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">log_message</span><span class="p">(</span><span class="nb">type</span><span class="p">):</span>
<span class="sd">''' Log message in the requested level</span>
<span class="sd"> @param type: log level</span>
<span class="sd"> '''</span>
<span class="k">if</span> <span class="nb">type</span> <span class="ow">not</span> <span class="ow">in</span> <span class="p">(</span><span class="s">'error'</span><span class="p">,</span> <span class="s">'warn'</span><span class="p">,</span> <span class="s">'info'</span><span class="p">,</span> <span class="s">'debug'</span><span class="p">,</span> <span class="s">'trace'</span><span class="p">):</span>
<span class="k">return</span> <span class="p">(</span><span class="bp">False</span><span class="p">,</span> <span class="s">'Not a valid log type'</span><span class="p">)</span>
<span class="n">param</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
<span class="n">message</span> <span class="o">=</span> <span class="n">param</span> <span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s">'message'</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">message</span><span class="p">:</span>
<span class="k">return</span> <span class="p">(</span><span class="bp">False</span><span class="p">,</span> <span class="s">'Invalid payload given'</span><span class="p">)</span>
<span class="k">return</span> <span class="p">(</span><span class="bp">True</span><span class="p">,</span> <span class="n">msg</span><span class="p">)</span>
<span class="nd">@app.route</span><span class="p">(</span><span class="s">'/sum/'</span><span class="p">,</span> <span class="n">method</span><span class="o">=</span><span class="p">[</span><span class="s">'PUT'</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">sum_data</span><span class="p">():</span>
<span class="sd">''' Load data from text file and print the sum of all number '''</span>
<span class="c"># Assuming it is a valid file (to make the guidance short and clear)</span>
<span class="n">file_data</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">form</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s">'file'</span><span class="p">,</span> <span class="bp">None</span><span class="p">)</span>
<span class="nb">sum</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">file_data</span><span class="p">:</span>
<span class="k">return</span> <span class="nb">sum</span>
<span class="c"># do some parsing of the file</span>
<span class="k">return</span> <span class="nb">sum</span></code></pre></div>
<p>Then to test the Flask routes you can either use:</p>
<ol>
<li><strong>Using Flask <code>url_for</code> method to let it to decide which function to use.</strong>
<em>(Reason: Maybe routes will change but function name don’t change, which in my opinion is bad design because for API, route usually dont change a lot of times.</em></li>
<li><strong>Using actual routes.</strong>
<em>(Reason: Most of the time function name will get renamed but routes stay the same)</em></li>
</ol>
<p><code>test_main.py</code></p>
<div class="highlight"><pre><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">unittest</span>
<span class="kn">from</span> <span class="nn">flask</span> <span class="kn">import</span> <span class="n">url_for</span>
<span class="k">class</span> <span class="nc">TestMain</span><span class="p">(</span><span class="n">unittest</span><span class="o">.</span><span class="n">TestCase</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">test_get_current_environment</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">resp</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url_for</span><span class="p">(</span><span class="s">'<module_name>.get_current_environment'</span><span class="p">))</span>
<span class="c"># or you can do it this way</span>
<span class="n">resp</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s">'/environment'</span><span class="p">)</span>
<span class="k">assert</span> <span class="n">resp</span><span class="o">.</span><span class="n">data</span> <span class="o">==</span> <span class="o"><</span><span class="n">app</span> <span class="n">env</span><span class="o">></span><span class="p">,</span> <span class="n">msg</span><span class="o">=</span><span class="s">'<some debug msg, know why/where>'</span>
<span class="k">def</span> <span class="nf">test_log_message</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">payload</span> <span class="o">=</span> <span class="o"><</span><span class="n">some_payload_here</span><span class="o">></span>
<span class="n">resp</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">url_for</span><span class="p">(</span><span class="s">'<module_name>.log_message'</span><span class="p">,</span> <span class="n">level</span><span class="o">=</span><span class="s">'debug'</span><span class="p">),</span>
<span class="n">data</span><span class="o">=</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">payload</span><span class="p">),</span> <span class="n">content_type</span><span class="o">=</span><span class="s">'application/json'</span><span class="p">)</span>
<span class="c"># or you can do it this way too (its your option which one is better)</span>
<span class="n">resp</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s">'/environment/debug'</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">payload</span><span class="p">),</span>
<span class="n">content_type</span><span class="o">=</span><span class="s">'application/json'</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">resp</span><span class="o">.</span><span class="n">data</span><span class="p">,</span> <span class="o"><</span><span class="n">valid</span> <span class="n">resp</span> <span class="n">data</span><span class="o">></span>
<span class="k">def</span> <span class="nf">test_sum_data</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">'count_sum.txt'</span><span class="p">,</span> <span class="s">'r'</span><span class="p">)</span> <span class="k">as</span> <span class="nb">file</span><span class="p">:</span>
<span class="n">payload</span> <span class="o">=</span> <span class="p">{</span><span class="s">'file'</span><span class="p">:</span> <span class="nb">file</span><span class="p">}</span>
<span class="n">resp</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">client</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="s">'/sum'</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="n">payload</span><span class="p">)</span>
<span class="c"># some assertion</span></code></pre></div>
<p>You can do assertion however you want, I recommend using built in <a href="https://docs.python.org/2/library/unittest.html">python unittest framework</a>.</p>
<p>Note that to be able to use flask <code>url_for</code> you have to activate the <a href="http://flask.pocoo.org/docs/testing/#other-testing-tricks">request context</a> which basically you can set with <code>self.app.test_request_context()</code></p>
<p>Lets go to another case when you wanted to modify flask <code>session</code> to speed up testing.
Mocking session cookie data is one good way of doing it where the session cookie might store something like <code>user_id</code> for authorization checking,
you can access and modify flask session as shown <a href="http://flask.pocoo.org/docs/testing/#accessing-and-modifying-sessions">here</a></p>
<div class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">test_client</span><span class="p">()</span> <span class="k">as</span> <span class="n">c</span><span class="p">:</span>
<span class="k">with</span> <span class="n">c</span><span class="o">.</span><span class="n">session_transaction</span><span class="p">()</span> <span class="k">as</span> <span class="n">sess</span><span class="p">:</span>
<span class="n">sess</span><span class="p">[</span><span class="s">'a_key'</span><span class="p">]</span> <span class="o">=</span> <span class="s">'a value'</span>
<span class="c"># once this is reached the session was stored</span></code></pre></div>
<p>Other Guide that I find useful for doing unit-test using mock</p>
<ul>
<li><a href="http://www.toptal.com/python/an-introduction-to-mocking-in-python#.">Introduction to mocking</a></li>
<li><a href="http://www.drdobbs.com/testing/using-mocks-in-python/240168251?pgno=1">More mocking article</a></li>
</ul>
<p>What do you guys think? If you found any mistake feel free to comment below :)</p>
<p><a href="http://saruberoz.github.io/unit-testing-flask-application/">Unit Testing Flask Application</a> was originally published by Wilson Sumanang at <a href="http://saruberoz.github.io">saruberoz' site</a> on July 11, 2014.</p>
http://saruberoz.github.io/hello-world
2014-06-25 07:13:48 +0000T00:00:00-00:00
2014-06-25T00:00:00+00:00
Wilson Sumanang
http://saruberoz.github.io
wilson.sumanang@yahoo.com
<p>Howdy all,</p>
<p>I have decided to remake my home page (where I can consistently improve) and have my own blog where I can learn new things and share stuff I have learned.</p>
<p>Trying to use Jekyll to create static web pages.</p>
<p>Let see how it goes, here I am trying to think of the stuff I want to write.</p>
<p><a href="http://saruberoz.github.io/hello-world/">Hello World!</a> was originally published by Wilson Sumanang at <a href="http://saruberoz.github.io">saruberoz' site</a> on June 25, 2014.</p>