Skip to content

Commit d8774cf

Browse files
authored
API: hosting integrations endpoint versioning/structure (#10216)
* API: hosting integrations endpoint versioning/structure Review initial JSON response to keep consistency with APIv3 resources. It uses a small modified version of the APIv3 serializers for known resources, removing some URL fields that can't be resolved from El Proxito. Sharing the same serializers that our APIv3 make our response a lot more consitent between the endpoints and allow us to expand the behavior by adding more fields in a structured way. Besides, these changes include a minimal checking of the version coming from `X-RTD-Hosting-Integrations-Version` header to decide which JSON structure return, allowing us to support multiple version at the same time in case we require to do some breaking changes. The JavaScript client is already sending this header in all of its requests. See readthedocs/addons#28 * Test: add json files used for API responses * Refactor: rename `client` to `addons` * Docs: todo comment about `features` key * API: use a `current` sub-key for known objects * json prettier * Tests: update structure * API: rename `features` field into `addons` * `features.search.enabled` was missing
1 parent 60c74d5 commit d8774cf

File tree

5 files changed

+334
-99
lines changed

5 files changed

+334
-99
lines changed
+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
{
2+
"comment": "THIS RESPONSE IS IN ALPHA FOR TEST PURPOSES ONLY AND IT'S GOING TO CHANGE COMPLETELY -- DO NOT USE IT!",
3+
"projects": {
4+
"current": {
5+
"created": "2019-04-29T10:00:00Z",
6+
"default_branch": "master",
7+
"default_version": "latest",
8+
"id": 1,
9+
"language": {
10+
"code": "en",
11+
"name": "English"
12+
},
13+
"modified": "2019-04-29T12:00:00Z",
14+
"name": "project",
15+
"programming_language": {
16+
"code": "words",
17+
"name": "Only Words"
18+
},
19+
"homepage": "http://project.com",
20+
"repository": {
21+
"type": "git",
22+
"url": "https://github.com/readthedocs/project"
23+
},
24+
"slug": "project",
25+
"subproject_of": null,
26+
"tags": ["project", "tag", "test"],
27+
"translation_of": null,
28+
"users": [
29+
{
30+
"username": "testuser"
31+
}
32+
]
33+
}
34+
},
35+
"versions": {
36+
"current": {
37+
"active": true,
38+
"hidden": false,
39+
"built": true,
40+
"downloads": {},
41+
"id": 1,
42+
"identifier": "a1b2c3",
43+
"ref": null,
44+
"slug": "latest",
45+
"type": "tag",
46+
"verbose_name": "latest"
47+
}
48+
},
49+
"builds": {
50+
"current": {
51+
"commit": "a1b2c3",
52+
"created": "2019-04-29T10:00:00Z",
53+
"duration": 60,
54+
"error": "",
55+
"finished": "2019-04-29T10:01:00Z",
56+
"id": 1,
57+
"project": "project",
58+
"state": {
59+
"code": "finished",
60+
"name": "Finished"
61+
},
62+
"success": true,
63+
"version": "latest"
64+
}
65+
},
66+
"domains": {
67+
"dashboard": "readthedocs.org"
68+
},
69+
"readthedocs": {
70+
"analytics": {
71+
"code": null
72+
}
73+
},
74+
"addons": {
75+
"analytics": {
76+
"enabled": true,
77+
"code": null
78+
},
79+
"external_version_warning": {
80+
"enabled": true,
81+
"query_selector": "[role=main]"
82+
},
83+
"non_latest_version_warning": {
84+
"enabled": true,
85+
"query_selector": "[role=main]",
86+
"versions": ["latest"]
87+
},
88+
"doc_diff": {
89+
"enabled": true,
90+
"base_url": "https://project.dev.readthedocs.io/en/latest/index.html",
91+
"root_selector": "[role=main]",
92+
"inject_styles": true,
93+
"base_host": "",
94+
"base_page": ""
95+
},
96+
"flyout": {
97+
"translations": [],
98+
"versions": [{ "slug": "latest", "url": "/en/latest/" }],
99+
"downloads": [],
100+
"vcs": {
101+
"url": "https://github.com",
102+
"username": "readthedocs",
103+
"repository": "test-builds",
104+
"branch": "a1b2c3",
105+
"filepath": "/docs/index.rst"
106+
}
107+
},
108+
"search": {
109+
"enabled": true,
110+
"api_endpoint": "/_/api/v3/search/",
111+
"default_filter": "subprojects:project/latest",
112+
"filters": [
113+
["Search only in this project", "project:project/latest"],
114+
["Search subprojects", "subprojects:project/latest"]
115+
],
116+
"project": "project",
117+
"version": "latest"
118+
}
119+
}
120+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"comment": "Undefined yet. Use v0 for now"
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"error": "The version specified in 'X-RTD-Hosting-Integrations-Version' is currently not supported"
3+
}
+58-81
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
"""Test hosting views."""
22

3+
import json
4+
from pathlib import Path
5+
36
import django_dynamic_fixture as fixture
47
import pytest
5-
from django.conf import settings
68
from django.contrib.auth.models import User
79
from django.test import TestCase, override_settings
810
from django.urls import reverse
911

10-
from readthedocs.builds.constants import EXTERNAL, INTERNAL, LATEST
12+
from readthedocs.builds.constants import LATEST
1113
from readthedocs.builds.models import Build
1214
from readthedocs.projects.constants import PUBLIC
1315
from readthedocs.projects.models import Project
@@ -21,114 +23,89 @@
2123
@pytest.mark.proxito
2224
class TestReadTheDocsConfigJson(TestCase):
2325
def setUp(self):
24-
self.user = fixture.get(User, username="user")
25-
self.user.set_password("user")
26+
self.user = fixture.get(User, username="testuser")
27+
self.user.set_password("testuser")
2628
self.user.save()
2729

2830
self.project = fixture.get(
2931
Project,
3032
slug="project",
33+
name="project",
3134
language="en",
3235
privacy_level=PUBLIC,
3336
external_builds_privacy_level=PUBLIC,
34-
repo="git://10.10.0.1/project",
37+
repo="https://github.com/readthedocs/project",
3538
programming_language="words",
3639
single_version=False,
3740
users=[self.user],
3841
main_language_project=None,
42+
project_url="http://project.com",
3943
)
44+
45+
for tag in ("tag", "project", "test"):
46+
self.project.tags.add(tag)
47+
4048
self.project.versions.update(
4149
privacy_level=PUBLIC,
4250
built=True,
4351
active=True,
44-
type=INTERNAL,
45-
identifier="1a2b3c",
52+
type="tag",
53+
identifier="a1b2c3",
4654
)
4755
self.version = self.project.versions.get(slug=LATEST)
4856
self.build = fixture.get(
4957
Build,
58+
project=self.project,
5059
version=self.version,
60+
commit="a1b2c3",
61+
length=60,
62+
state="finished",
63+
success=True,
64+
)
65+
66+
def _get_response_dict(self, view_name, filepath=None):
67+
filepath = filepath or __file__
68+
filename = Path(filepath).absolute().parent / "responses" / f"{view_name}.json"
69+
return json.load(open(filename))
70+
71+
def _normalize_datetime_fields(self, obj):
72+
obj["projects"]["current"]["created"] = "2019-04-29T10:00:00Z"
73+
obj["projects"]["current"]["modified"] = "2019-04-29T12:00:00Z"
74+
obj["builds"]["current"]["created"] = "2019-04-29T10:00:00Z"
75+
obj["builds"]["current"]["finished"] = "2019-04-29T10:01:00Z"
76+
return obj
77+
78+
def test_get_config_v0(self):
79+
r = self.client.get(
80+
reverse("proxito_readthedocs_config_json"),
81+
{"url": "https://project.dev.readthedocs.io/en/latest/"},
82+
secure=True,
83+
HTTP_HOST="project.dev.readthedocs.io",
84+
HTTP_X_RTD_HOSTING_INTEGRATIONS_VERSION="0.1.0",
85+
)
86+
assert r.status_code == 200
87+
assert self._normalize_datetime_fields(r.json()) == self._get_response_dict(
88+
"v0"
5189
)
5290

53-
def test_get_config(self):
91+
def test_get_config_v1(self):
5492
r = self.client.get(
5593
reverse("proxito_readthedocs_config_json"),
5694
{"url": "https://project.dev.readthedocs.io/en/latest/"},
5795
secure=True,
5896
HTTP_HOST="project.dev.readthedocs.io",
97+
HTTP_X_RTD_HOSTING_INTEGRATIONS_VERSION="1.0.0",
5998
)
6099
assert r.status_code == 200
100+
assert r.json() == self._get_response_dict("v1")
61101

62-
expected = {
63-
"comment": "THIS RESPONSE IS IN ALPHA FOR TEST PURPOSES ONLY AND IT'S GOING TO CHANGE COMPLETELY -- DO NOT USE IT!",
64-
"project": {
65-
"slug": self.project.slug,
66-
"language": self.project.language,
67-
"repository_url": self.project.repo,
68-
"programming_language": self.project.programming_language,
69-
},
70-
"version": {
71-
"slug": self.version.slug,
72-
"external": self.version.type == EXTERNAL,
73-
},
74-
"build": {
75-
"id": self.build.pk,
76-
},
77-
"domains": {
78-
"dashboard": settings.PRODUCTION_DOMAIN,
79-
},
80-
"readthedocs": {
81-
"analytics": {
82-
"code": None,
83-
}
84-
},
85-
"features": {
86-
"analytics": {
87-
"code": None,
88-
},
89-
"external_version_warning": {
90-
"enabled": True,
91-
"query_selector": "[role=main]",
92-
},
93-
"non_latest_version_warning": {
94-
"enabled": True,
95-
"query_selector": "[role=main]",
96-
"versions": [
97-
"latest",
98-
],
99-
},
100-
"doc_diff": {
101-
"enabled": True,
102-
"base_url": "https://project.dev.readthedocs.io/en/latest/index.html",
103-
"root_selector": "[role=main]",
104-
"inject_styles": True,
105-
"base_host": "",
106-
"base_page": "",
107-
},
108-
"flyout": {
109-
"translations": [],
110-
"versions": [
111-
{"slug": "latest", "url": "/en/latest/"},
112-
],
113-
"downloads": [],
114-
"vcs": {
115-
"url": "https://github.com",
116-
"username": "readthedocs",
117-
"repository": "test-builds",
118-
"branch": self.version.identifier,
119-
"filepath": "/docs/index.rst",
120-
},
121-
},
122-
"search": {
123-
"api_endpoint": "/_/api/v3/search/",
124-
"default_filter": "subprojects:project/latest",
125-
"filters": [
126-
["Search only in this project", "project:project/latest"],
127-
["Search subprojects", "subprojects:project/latest"],
128-
],
129-
"project": "project",
130-
"version": "latest",
131-
},
132-
},
133-
}
134-
assert r.json() == expected
102+
def test_get_config_unsupported_version(self):
103+
r = self.client.get(
104+
reverse("proxito_readthedocs_config_json"),
105+
{"url": "https://project.dev.readthedocs.io/en/latest/"},
106+
secure=True,
107+
HTTP_HOST="project.dev.readthedocs.io",
108+
HTTP_X_RTD_HOSTING_INTEGRATIONS_VERSION="2.0.0",
109+
)
110+
assert r.status_code == 400
111+
assert r.json() == self._get_response_dict("v2")

0 commit comments

Comments
 (0)