8000 Merge pull request #154 from daveta/daveta-django-tests · rsliang/botbuilder-python@f462db6 · GitHub
[go: up one dir, main page]

Skip to content

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f462db6

Browse files
authored
Merge pull request microsoft#154 from daveta/daveta-django-tests
Add Application Insights Django-specific tests
2 parents c56d828 + d2a1e37 commit f462db6

File tree

13 files changed

+772
-35
lines changed

13 files changed

+772
-35
lines changed

libraries/botbuilder-applicationinsights/botbuilder/applicationinsights/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
# license information.
66
# --------------------------------------------------------------------------
77

8-
from .application_insights_telemetry_client import ApplicationInsightsTelemetryClient
8+
from .application_insights_telemetry_client import ApplicationInsightsTelemetryClient, bot_telemetry_processor
99

10-
__all__ = ["ApplicationInsightsTelemetryClient"]
10+
__all__ = ["ApplicationInsightsTelemetryClient",
11+
"bot_telemetry_processor"
12+
]

libraries/botbuilder-applicationinsights/botbuilder/applicationinsights/application_insights_telemetry_client.py

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,35 @@
77
from typing import Dict
88
from .integration_post_data import IntegrationPostData
99

10+
def bot_telemetry_processor(data, context):
11+
post_data = IntegrationPostData().activity_json
12+
if post_data is None:
13+
return
14+
# Override session and user id
15+
from_prop = post_data['from'] if 'from' in post_data else None
16+
user_id = from_prop['id'] if from_prop != None else None
17+
channel_id = post_data['channelId'] if 'channelId' in post_data else None
18+
conversation = post_data['conversation'] if 'conversation' in post_data else None
19+
conversation_id = conversation['id'] if 'id' in conversation else None
20+
context.user.id = channel_id + user_id
21+
context.session.id = conversation_id
22+
23+
# Additional bot-specific properties
24+
if 'id' in post_data:
25+
data.properties["activityId"] = post_data['id']
26+
if 'channelId' in post_data:
27+
data.properties["channelId"] = post_data['channelId']
28+
if 'type' in post_data:
29+
data.properties["activityType"] = post_data['type']
30+
31+
1032
class ApplicationInsightsTelemetryClient(BotTelemetryClient):
1133

12-
def __init__(self, instrumentation_key:str):
34+
def __init__(self, instrumentation_key:str, telemetry_client: TelemetryClient = None):
1335
self._instrumentation_key = instrumentation_key
14-
self._client = TelemetryClient(self._instrumentation_key)
36+
self._client = telemetry_client if telemetry_client != None else TelemetryClient(self._instrumentation_key)
1537
# Telemetry Processor
16-
def telemetry_processor(data, context):
17-
post_data = IntegrationPostData().activity_json
18-
# Override session and user id
19-
from_prop = post_data['from'] if 'from' in post_data else None
20-
user_id = from_prop['id'] if from_prop != None else None
21-
channel_id = post_data['channelId'] if 'channelId' in post_data else None
22-
conversation = post_data['conversation'] if 'conversation' in post_data else None
23-
conversation_id = conversation['id'] if 'id' in conversation else None
24-
context.user.id = channel_id + user_id
25-
context.session.id = conversation_id
26-
27-
# Additional bot-specific properties
28-
if 'activityId' in post_data:
29-
data.properties["activityId"] = post_data['activityId']
30-
if 'channelId' in post_data:
31-
data.properties["channelId"] = post_data['channelId']
32-
if 'activityType' in post_data:
33-
data.properties["activityType"] = post_data['activityType']
34-
35-
self._client.add_telemetry_processor(telemetry_processor)
38+
self._client.add_telemetry_processor(bot_telemetry_processor)
3639

3740

3841
def track_pageview(self, name: str, url:str, duration: int = 0, properties : Dict[str, object]=None,
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from .bot_telemetry_middleware import BotTelemetryMiddleware, retrieve_bot_body
5+
6+
__all__ = [
7+
"BotTelemetryMiddleware",
8+
"retrieve_bot_body"
9+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
import sys
4+
import json
5+
from threading import current_thread
6+
7+
# Map of thread id => POST body text
8+
_request_bodies = {}
9+
10+
def retrieve_bot_body():
11+
""" retrieve_bot_body
12+
Retrieve the POST body text from temporary cache.
13+
The POST body corresponds with the thread id and should resides in
14+
cache just for lifetime of request.
15+
16+
TODO: Add cleanup job to kill orphans
17+
"""
18+
result = _request_bodies.pop(current_thread().ident, None)
19+
return result
20+
21+
class BotTelemetryMiddleware():
22+
"""
23+
Save off the POST body to later populate bot properties
24+
"""
25+
def __init__(self, get_response):
26+
self.get_response = get_response
27+
28+
def __call__(self, request):
29+
self.process_request(request)
30+
return self.get_response(request)
31+
32+
def process_request(self, request):
33+
body_unicode = request.body.decode('utf-8') if request.method == "POST" else None
34+
# Sanity check JSON
35+
if body_unicode != None:
36+
try:
37+
body = json.loads(body_unicode)
38+
except:
39+
return
40+
# Integration layer expecting just the json text.
41+
_request_bodies[current_thread().ident] = body_unicode
42+
43+

libraries/botbuilder-applicationinsights/botbuilder/applicationinsights/integration_post_data.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import imp
77
import json
88
from botbuilder.schema import Activity
9+
from botbuilder.applicationinsights.django import retrieve_bot_body
910

1011
class IntegrationPostData:
1112
"""
@@ -44,15 +45,8 @@ def get_request_body(self) -> str:
4445
return body
4546
else:
4647
if self.detect_django():
47-
mod = __import__('django.http', fromlist=['http'])
48-
http_request = getattr(mod, 'HttpRequest')
49-
django_requests = [o for o in gc.get_objects() if isinstance(o, http_request)]
50-
django_request_instances = len(django_requests)
51-
if django_request_instances != 1:
52-
raise Exception(f'Detected {django_request_instances} instances of Django Requests. Expecting 1.')
53-
request = django_requests[0]
54-
body_unicode = request.body.decode('utf-8') if request.method == "POST" else None
55-
return body_unicode
48+
# Retrieve from Middleware cache
49+
return retrieve_bot_body()
5650

5751
def body_from_WSGI_environ(self, environ):
5852
try:
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# DJANGO-specific tests
2+
Django generates *code* to create projects (`django-admin startproject`) and apps. For testing, we test the generated code. The tests are bare-bones to be compatible across different versions of django.
3+
4+
- This project contains a script to execute tests against currently supported version(s) of python and django.
5+
- Assume latest version of Application Insights.
6+
- Relies on virtualenv to run all tests.
7+
- Uses django commands to generate new project and execute django tests.
8+
- To run, first `cd django_tests` and then `bash .\all_tests.sh` (ie, in Powershell) to run all permutations.
9+
10+
File | | Description
11+
--- | ---
12+
all_tests.sh | Runs our current test matrix of python/django versions. Current matrix is python (3.6) and django (2.2).
13+
README.md | This file.
14+
run_test.sh | Runs specific python/django version to create project, copy replacement files and runs tests.
15+
template.html | Template file
16+
tests.py | Django tests.
17+
urls.py | url paths called by tests
18+
views.py | paths that are called
19+
20+
21+
22+
23+
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/bin/bash
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License.
4+
5+
if [ -z $PYTHON ]; then
6+
PYTHON=$(which python)
7+
fi
8+
9+
cd $(dirname $0)
10+
BASEDIR=$(pwd)
11+
12+
# Django/python compatibility matrix...
13+
if $PYTHON -c "import sys; sys.exit(1 if (sys.version_info.major == 3 and sys.version_info.minor == 6) else 0)"; then
14+
echo "[Error] Environment should be configured with Python 3.6!" 1>&2
15+
exit 2
16+
fi
17+
# Add more versions here (space delimited).
18+
DJANGO_VERSIONS='2.2'
19+
20+
# For each Django version...
21+
for v in $DJANGO_VERSIONS
22+
do
23+
echo ""
24+
echo "***"
25+
echo "*** Running tests for Django $v"
26+
echo "***"
27+
echo ""
28+
29+
# Create new directory
30+
TMPDIR=$(mktemp -d)
31+
function cleanup
32+
{
33+
rm -rf $TMPDIR
34+
exit $1
35+
}
36+
37+
trap cleanup EXIT SIGINT
38+
39+
# Create virtual environment
40+
$PYTHON -m venv $TMPDIR/env
41+
42+
# Install Django version + application insights
43+
. $TMPDIR/env/bin/activate
44+
pip install Django==$v || exit $?
45+
cd $BASEDIR/..
46+
pip install . || exit $?
47+
48+
# Run tests
49+
cd $BASEDIR
50+
bash ./run_test.sh || exit $?
51+
52+
# Deactivate
53+
# (Windows may complain since doesn't add deactivate to path properly)
54+
deactivate
55+
56+
# Remove venv
57+
rm -rf $TMPDIR
58+
done
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/bin/bash
2+
3+
# Copyright (c) Microsoft Corporation. All rights reserved.
4+
# Licensed under the MIT License.
5+
6+
# It is expected at this point that django and applicationinsights are both installed into a
7+
# virtualenv.
8+
django_version=$(python -c "import django ; print('.'.join(map(str, django.VERSION[0:2])))")
9+
test $? -eq 0 || exit 1
10+
11+
# Create a new temporary work directory
12+
TMPDIR=$(mktemp -d)
13+
SRCDIR=$(pwd)
14+
function cleanup
15+
{
16+
cd $SRCDIR
17+
rm -rf $TMPDIR
18+
exit $1
19+
}
20+
trap cleanup EXIT SIGINT
21+
cd $TMPDIR
22+
23+
# Set up Django project
24+
django-admin startproject aitest
25+
cd aitest
26+
cp $SRCDIR/views.py aitest/views.py
27+
cp $SRCDIR/tests.py aitest/tests.py
28+
cp $SRCDIR/urls.py aitest/urls.py
29+
cp $SRCDIR/template.html aitest/template.html
30+
31+
./manage.py test
32+
exit $?
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Test django template: {{ context }}

0 commit comments

Comments
 (0)
0