Browse Source

Merge pull request #69 from chakki-works/travis-ci

Support Travis CI
pull/74/head
Hiroki Nakayama 5 years ago
committed by GitHub
parent
commit
bd3eccd147
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 109 additions and 217 deletions
  1. 9
      .travis.yml
  2. 2
      README.md
  3. 2
      app/app/settings.py
  4. 152
      app/server/tests/test_annotation_api.py
  5. 40
      app/server/tests/test_models.py
  6. 121
      app/server/tests/test_projects_api.py

9
.travis.yml

@ -0,0 +1,9 @@
language: python
python:
- "3.6"
install:
- pip install -r requirements.txt
script:
- python app/manage.py migrate
- python app/manage.py collectstatic
- python app/manage.py test server.tests

2
README.md

@ -1,5 +1,7 @@
# doccano
[![Build Status](https://travis-ci.org/chakki-works/doccano.svg?branch=master)](https://travis-ci.org/chakki-works/doccano)
doccano is an open source text annotation tool for human. It provides annotation features for text classification, sequence labeling and sequence to sequence. So, you can create labeled data for sentiment analysis, named entity recognition, text summarization and so on. Just create project, upload data and start annotation. You can build dataset in hours.
## Demo

2
app/app/settings.py

@ -163,4 +163,4 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
# on the import phase
IMPORT_BATCH_SIZE = 500
django_heroku.settings(locals())
django_heroku.settings(locals(), test_runner=False)

152
app/server/tests/test_annotation_api.py

@ -6,120 +6,68 @@ from ..models import *
class TestAnnotationAPI(APITestCase):
def setUp(self):
self.username, self.password = 'user', 'pass'
def create_user(self):
user = User.objects.create_user(username=self.username, password=self.password)
return user
def create_superuser(self):
user = User.objects.create_superuser(username=self.username,
password=self.password,
email='hoge@example.com')
return user
def create_project(self):
project = mixer.blend('server.Project')
return project
def create_label(self):
label = mixer.blend('server.Label')
@classmethod
def setUpTestData(cls):
cls.username = 'user'
cls.password = 'pass'
cls.user1 = User.objects.create_user(username=cls.username, password=cls.password)
cls.user2 = User.objects.create_user(username='user2', password='pass2')
cls.project1 = mixer.blend('server.Project', project_type=Project.DOCUMENT_CLASSIFICATION,
users=[cls.user1, cls.user2])
cls.project2 = mixer.blend('server.Project', project_type=Project.DOCUMENT_CLASSIFICATION,
users=[cls.user2])
cls.doc1 = mixer.blend('server.Document', project=cls.project1)
cls.doc2 = mixer.blend('server.Document', project=cls.project1)
cls.label = mixer.blend('server.Label', project=cls.project1)
cls.annotation1 = mixer.blend('server.DocumentAnnotation', document=cls.doc1, user=cls.user1)
cls.annotation2 = mixer.blend('server.DocumentAnnotation', document=cls.doc1, user=cls.user2)
return label
def create_doc(self):
doc = mixer.blend('server.Document')
return doc
def create_annotation(self):
annotation = mixer.blend('server.DocumentAnnotation')
return annotation
def setUp(self):
self.client.login(username=self.username, password=self.password)
def test_get_own_annotation(self):
def test_fetch_own_annotation(self):
"""
Ensure we can get own annotation objects.
Ensure user can fetch only own annotation.
"""
user = self.create_user()
project = self.create_project()
project.project_type = Project.DOCUMENT_CLASSIFICATION
annotation = self.create_annotation()
annotation.user = user
project.users.add(user)
project.documents.add(annotation.document)
project.save()
annotation.save()
url = reverse('annotations', args=[project.id, annotation.document.id])
url = reverse('annotations', args=[self.project1.id, self.doc1.id])
r = self.client.get(url, format='json')
self.assertEqual(r.status_code, status.HTTP_200_OK)
self.assertEqual(len(r.data), 1)
self.assertEqual(r.data[0]['id'], self.annotation1.id)
self.client.login(username=self.username, password=self.password)
response = self.client.get(url, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIsInstance(response.data, list)
self.assertEqual(response.data[0]['id'], annotation.id)
def test_get_others_annotation(self):
def test_fetch_other_projects_annotation(self):
"""
Ensure we cannot get others annotation objects.
Ensure user cannot fetch other project's annotation.
"""
user = self.create_user()
project = self.create_project()
project.project_type = Project.DOCUMENT_CLASSIFICATION
annotation = self.create_annotation()
project.users.add(annotation.user)
project.documents.add(annotation.document)
project.save()
annotation.save()
url = reverse('annotations', args=[project.id, annotation.document.id])
self.client.login(username=self.username, password=self.password)
response = self.client.get(url, format='json')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
url = reverse('annotations', args=[self.project2.id, self.doc1.id])
r = self.client.get(url, format='json')
self.assertEqual(r.status_code, status.HTTP_403_FORBIDDEN)
def test_create_annotation(self):
def test_annotate_doc(self):
"""
Ensure we can create a new annotation object.
Ensure user can annotate a document.
"""
user = self.create_user()
project = self.create_project()
doc = self.create_doc()
label = self.create_label()
project.project_type = Project.DOCUMENT_CLASSIFICATION
project.users.add(user)
project.documents.add(doc)
project.labels.add(label)
data = {'label_id': label.id}
url = reverse('annotations', args=[project.id, doc.id])
self.client.login(username=self.username, password=self.password)
response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(DocumentAnnotation.objects.count(), 1)
# Try to annotate a empty document(doc2).
data = {'label': self.label.id}
url = reverse('annotations', args=[self.project1.id, self.doc2.id])
r = self.client.post(url, data, format='json')
self.assertEqual(r.status_code, status.HTTP_201_CREATED)
self.assertEqual(len(self.doc2.doc_annotations.all()), 1)
def test_delete_annotation(self):
"""
Ensure we cannot create a new project object by user.
Ensure user can delete only own annotation.
"""
user = self.create_user()
project = self.create_project()
project.project_type = Project.DOCUMENT_CLASSIFICATION
annotation = self.create_annotation()
annotation.user = user
project.users.add(annotation.user)
project.documents.add(annotation.document)
project.save()
annotation.save()
url = reverse('ann', args=[project.id, annotation.document.id, annotation.id])
self.assertEqual(DocumentAnnotation.objects.count(), 1)
self.client.login(username=self.username, password=self.password)
response = self.client.delete(url, format='json')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
self.assertEqual(DocumentAnnotation.objects.count(), 0)
self.assertEqual(len(self.doc1.doc_annotations.all()), 2)
# Try to delete own annotation.
url = reverse('ann', args=[self.project1.id, self.doc1.id, self.annotation1.id])
r = self.client.delete(url, format='json')
self.assertEqual(r.status_code, status.HTTP_204_NO_CONTENT)
self.assertEqual(len(self.doc1.doc_annotations.all()), 1)
# Try to delete other's annotation.
url = reverse('ann', args=[self.project1.id, self.doc1.id, self.annotation2.id])
r = self.client.delete(url, format='json')
self.assertEqual(r.status_code, status.HTTP_403_FORBIDDEN)

40
app/server/tests/test_models.py

@ -2,6 +2,7 @@ from django.test import TestCase
from django.core.exceptions import ValidationError
from django.db.utils import IntegrityError
from mixer.backend.django import mixer
from server.models import Label, DocumentAnnotation, SequenceAnnotation, Seq2seqAnnotation
class TestProject(TestCase):
@ -12,7 +13,7 @@ class TestProject(TestCase):
def test_get_progress(self):
project = mixer.blend('server.Project')
res = project.get_progress()
res = project.get_progress(None)
self.assertEqual(res['total'], 0)
self.assertEqual(res['remaining'], 0)
@ -23,41 +24,33 @@ class TestLabel(TestCase):
label = mixer.blend('server.Label')
mixer.blend('server.Label', shortcut=label.shortcut)
with self.assertRaises(IntegrityError):
mixer.blend('server.Label',
project=label.project,
shortcut=label.shortcut)
Label(project=label.project, shortcut=label.shortcut).save()
def test_text_uniqueness(self):
label = mixer.blend('server.Label')
mixer.blend('server.Label', text=label.text)
with self.assertRaises(IntegrityError):
mixer.blend('server.Label',
project=label.project,
text=label.text)
Label(project=label.project, text=label.text).save()
class TestDocumentAnnotation(TestCase):
def test_uniqueness(self):
annotation1 = mixer.blend('server.DocumentAnnotation')
a = mixer.blend('server.DocumentAnnotation')
with self.assertRaises(IntegrityError):
mixer.blend('server.DocumentAnnotation',
document=annotation1.document,
user=annotation1.user,
label=annotation1.label)
DocumentAnnotation(document=a.document, user=a.user, label=a.label).save()
class TestSequenceAnnotation(TestCase):
def test_uniqueness(self):
annotation1 = mixer.blend('server.SequenceAnnotation')
a = mixer.blend('server.SequenceAnnotation')
with self.assertRaises(IntegrityError):
mixer.blend('server.SequenceAnnotation',
document=annotation1.document,
user=annotation1.user,
label=annotation1.label,
start_offset=annotation1.start_offset,
end_offset=annotation1.end_offset)
SequenceAnnotation(document=a.document,
user=a.user,
label=a.label,
start_offset=a.start_offset,
end_offset=a.end_offset).save()
def test_position_constraint(self):
with self.assertRaises(ValidationError):
@ -68,9 +61,8 @@ class TestSequenceAnnotation(TestCase):
class TestSeq2seqAnnotation(TestCase):
def test_uniqueness(self):
annotation1 = mixer.blend('server.Seq2seqAnnotation')
a = mixer.blend('server.Seq2seqAnnotation')
with self.assertRaises(IntegrityError):
mixer.blend('server.Seq2seqAnnotation',
document=annotation1.document,
user=annotation1.user,
text=annotation1.text)
Seq2seqAnnotation(document=a.document,
user=a.user,
text=a.text).save()

121
app/server/tests/test_projects_api.py

@ -7,59 +7,37 @@ from ..models import *
class TestProjects(APITestCase):
def setUp(self):
self.username, self.password = 'user', 'pass'
self.url = reverse('project-list')
def create_user(self):
user = User.objects.create_user(username=self.username, password=self.password)
return user
def create_superuser(self):
user = User.objects.create_superuser(username=self.username,
password=self.password,
email='hoge@example.com')
return user
@classmethod
def setUpTestData(cls):
cls.username = 'user'
cls.password = 'pass'
cls.super_username = 'super'
cls.normal_user = User.objects.create_user(username=cls.username, password=cls.password)
cls.super_user = User.objects.create_superuser(username=cls.super_username,
password=cls.password, email='fizz@buzz.com')
cls.project1 = mixer.blend('server.Project', project_type=Project.DOCUMENT_CLASSIFICATION,
users=[cls.normal_user, cls.super_user])
cls.project2 = mixer.blend('server.Project', project_type=Project.DOCUMENT_CLASSIFICATION,
users=[cls.super_user])
cls.url = reverse('project-list')
def create_project(self):
project = mixer.blend('server.Project')
return project
def add_user_to_project(self, user, project):
project.users.add(user)
def setUp(self):
self.client.login(username=self.username, password=self.password)
def test_get_projects(self):
"""
Ensure we can get project objects.
Ensure user can get project.
"""
user = self.create_user()
project = self.create_project()
self.add_user_to_project(user, project)
self.client.login(username=self.username, password=self.password)
response = self.client.get(self.url, format='json')
p = response.data[0]
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIsInstance(response.data, list)
self.assertEqual(p['id'], project.id)
self.assertEqual(p['name'], project.name)
self.assertEqual(p['description'], project.description)
self.assertEqual(p['project_type'], project.project_type)
self.assertEqual(p['image'], project.image)
self.assertEqual(len(response.data), 1)
self.assertEqual(response.data[0]['id'], self.project1.id)
def test_get_progress(self):
"""
Ensure we can get project's progress.
Ensure user can get project's progress.
"""
user = self.create_user()
project = self.create_project()
self.add_user_to_project(user, project)
url = '{}{}/progress/'.format(self.url, project.id)
self.client.login(username=self.username, password=self.password)
url = '{}{}/progress/'.format(self.url, self.project1.id)
response = self.client.get(url, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
@ -68,60 +46,23 @@ class TestProjects(APITestCase):
self.assertIsInstance(response.data['total'], int)
self.assertIsInstance(response.data['remaining'], int)
def test_create_project_by_admin(self):
"""
Ensure we can create a new project object by admin.
"""
user = self.create_superuser()
data = {'name': 'DabApps',
'description': 'desc',
'project_type': Project.DOCUMENT_CLASSIFICATION,
'users': [user.id]}
self.client.login(username=self.username, password=self.password)
response = self.client.post(self.url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Project.objects.count(), 1)
self.assertEqual(Project.objects.get().name, 'DabApps')
def test_create_project_by_user(self):
"""
Ensure we cannot create a new project object by user.
"""
user = self.create_user()
data = {'name': 'DabApps',
'description': 'desc',
'project_type': Project.DOCUMENT_CLASSIFICATION,
'users': [user.id]}
self.client.login(username=self.username, password=self.password)
response = self.client.post(self.url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_delete_project_by_superuser(self):
def test_superuser_can_delete_project(self):
"""
Ensure we can delete a project by superuser.
Ensure superuser can delete a project.
"""
user = self.create_superuser()
project = self.create_project()
self.assertEqual(Project.objects.count(), 1)
self.client.login(username=self.username, password=self.password)
url = '{}{}/'.format(self.url, project.id)
self.assertEqual(Project.objects.count(), 2)
self.client.login(username=self.super_username, password=self.password)
url = '{}{}/'.format(self.url, self.project2.id)
response = self.client.delete(url, format='json')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
self.assertEqual(Project.objects.count(), 0)
self.assertEqual(Project.objects.count(), 1)
def test_delete_project_by_user(self):
def test_normal_user_cannot_delete_project(self):
"""
Ensure we cannot delete a project by user.
Ensure normal user cannot delete a project.
"""
user = self.create_user()
project = self.create_project()
self.assertEqual(Project.objects.count(), 1)
self.client.login(username=self.username, password=self.password)
url = '{}{}/'.format(self.url, project.id)
self.assertEqual(Project.objects.count(), 2)
url = '{}{}/'.format(self.url, self.project2.id)
response = self.client.delete(url, format='json')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(Project.objects.count(), 1)
self.assertEqual(Project.objects.count(), 2)
Loading…
Cancel
Save