|
5 | 5 | import os
|
6 | 6 | import shutil
|
7 | 7 | import tempfile
|
8 |
| -import collections |
9 |
| -from functools import wraps |
10 | 8 |
|
11 | 9 | import mock
|
12 | 10 | from django.conf import settings
|
| 11 | +from django.core.urlresolvers import reverse |
13 | 12 | from django.test import TestCase, override_settings
|
14 | 13 | from django_dynamic_fixture import get
|
15 | 14 |
|
16 | 15 | from readthedocs.builds.models import Version
|
17 | 16 | from readthedocs.projects.models import Project, Domain
|
| 17 | +from readthedocs.projects.tasks import symlink_project |
18 | 18 | from readthedocs.core.symlink import PublicSymlink, PrivateSymlink
|
19 | 19 |
|
20 | 20 |
|
@@ -908,3 +908,202 @@ def test_symlink_no_error(self):
|
908 | 908 | self.symlink.run()
|
909 | 909 | except:
|
910 | 910 | self.fail('Symlink run raised an exception on unicode slug')
|
| 911 | + |
| 912 | + def test_symlink_broadcast_calls_on_project_save(self): |
| 913 | + """ |
| 914 | + Test calls to ``readthedocs.core.utils.broadcast`` on Project.save(). |
| 915 | +
|
| 916 | + When a Project is saved, we need to check that we are calling |
| 917 | + ``broadcast`` utility with the proper task and arguments to re-symlink |
| 918 | + them. |
| 919 | + """ |
| 920 | + with mock.patch('readthedocs.projects.models.broadcast') as broadcast: |
| 921 | + project = get(Project) |
| 922 | + # skipped on first save |
| 923 | + broadcast.assert_not_called() |
| 924 | + |
| 925 | + broadcast.reset_mock() |
| 926 | + project.description = 'New description' |
| 927 | + project.save() |
| 928 | + # called once for this project itself |
| 929 | + broadcast.assert_any_calls( |
| 930 | + type='app', |
| 931 | + task=symlink_project, |
| 932 | + args=[project.pk], |
| 933 | + ) |
| 934 | + |
| 935 | + broadcast.reset_mock() |
| 936 | + subproject = get(Project) |
| 937 | + # skipped on first save |
| 938 | + broadcast.assert_not_called() |
| 939 | + |
| 940 | + project.add_subproject(subproject) |
| 941 | + # subproject.save() is not called |
| 942 | + broadcast.assert_not_called() |
| 943 | + |
| 944 | + subproject.description = 'New subproject description' |
| 945 | + subproject.save() |
| 946 | + # subproject symlinks |
| 947 | + broadcast.assert_any_calls( |
| 948 | + type='app', |
| 949 | + task=symlink_project, |
| 950 | + args=[subproject.pk], |
| 951 | + ) |
| 952 | + # superproject symlinks |
| 953 | + broadcast.assert_any_calls( |
| 954 | + type='app', |
| 955 | + task=symlink_project, |
| 956 | + args=[project.pk], |
| 957 | + ) |
| 958 | + |
| 959 | + |
| 960 | +@override_settings() |
| 961 | +class TestPublicPrivateSymlink(TempSiterootCase, TestCase): |
| 962 | + |
| 963 | + def setUp(self): |
| 964 | + super(TestPublicPrivateSymlink, self).setUp() |
| 965 | + from django.contrib.auth.models import User |
| 966 | + |
| 967 | + self.user = get(User) |
| 968 | + self.project = get( |
| 969 | + Project, name='project', slug='project', privacy_level='public', |
| 970 | + users=[self.user], main_language_project=None) |
| 971 | + self.project.versions.update(privacy_level='public') |
| 972 | + self.project.save() |
| 973 | + |
| 974 | + self.subproject = get( |
| 975 | + Project, name='subproject', slug='subproject', privacy_level='public', |
| 976 | + users=[self.user], main_language_project=None) |
| 977 | + self.subproject.versions.update(privacy_level='public') |
| 978 | + self.subproject.save() |
| 979 | + |
| 980 | + def test_change_subproject_privacy(self): |
| 981 | + """ |
| 982 | + Change subproject's ``privacy_level`` creates proper symlinks. |
| 983 | +
|
| 984 | + When the ``privacy_level`` changes in the subprojects, we need to |
| 985 | + re-symlink the superproject also to keep in sync its symlink under the |
| 986 | + private/public roots. |
| 987 | + """ |
| 988 | + filesystem_before = { |
| 989 | + 'private_cname_project': {}, |
| 990 | + 'private_cname_root': {}, |
| 991 | + 'private_web_root': { |
| 992 | + 'project': { |
| 993 | + 'en': {}, |
| 994 | + }, |
| 995 | + 'subproject': { |
| 996 | + 'en': {}, |
| 997 | + }, |
| 998 | + }, |
| 999 | + 'public_cname_project': {}, |
| 1000 | + 'public_cname_root': {}, |
| 1001 | + 'public_web_root': { |
| 1002 | + 'project': { |
| 1003 | + 'en': { |
| 1004 | + 'latest': { |
| 1005 | + 'type': 'link', |
| 1006 | + 'target': 'user_builds/project/rtd-builds/latest', |
| 1007 | + }, |
| 1008 | + }, |
| 1009 | + 'projects': { |
| 1010 | + 'subproject': { |
| 1011 | + 'type': 'link', |
| 1012 | + 'target': 'public_web_root/subproject', |
| 1013 | + }, |
| 1014 | + }, |
| 1015 | + }, |
| 1016 | + 'subproject': { |
| 1017 | + 'en': { |
| 1018 | + 'latest': { |
| 1019 | + 'type': 'link', |
| 1020 | + 'target': 'user_builds/subproject/rtd-builds/latest', |
| 1021 | + }, |
| 1022 | + }, |
| 1023 | + }, |
| 1024 | + }, |
| 1025 | + } |
| 1026 | + |
| 1027 | + filesystem_after = { |
| 1028 | + 'private_cname_project': {}, |
| 1029 | + 'private_cname_root': {}, |
| 1030 | + 'private_web_root': { |
| 1031 | + 'project': { |
| 1032 | + 'en': {}, |
| 1033 | + 'projects': { |
| 1034 | + 'subproject': { |
| 1035 | + 'type': 'link', |
| 1036 | + 'target': 'private_web_root/subproject', |
| 1037 | + }, |
| 1038 | + }, |
| 1039 | + }, |
| 1040 | + 'subproject': { |
| 1041 | + 'en': { |
| 1042 | + 'latest': { |
| 1043 | + 'type': 'link', |
| 1044 | + 'target': 'user_builds/subproject/rtd-builds/latest', |
| 1045 | + }, |
| 1046 | + }, |
| 1047 | + }, |
| 1048 | + }, |
| 1049 | + 'public_cname_project': {}, |
| 1050 | + 'public_cname_root': {}, |
| 1051 | + 'public_web_root': { |
| 1052 | + 'project': { |
| 1053 | + 'en': { |
| 1054 | + 'latest': { |
| 1055 | + 'type': 'link', |
| 1056 | + 'target': 'user_builds/project/rtd-builds/latest', |
| 1057 | + }, |
| 1058 | + }, |
| 1059 | + 'projects': {}, |
| 1060 | + }, |
| 1061 | + 'subproject': { |
| 1062 | + 'en': {}, |
| 1063 | + }, |
| 1064 | + }, |
| 1065 | + } |
| 1066 | + |
| 1067 | + self.assertEqual(self.project.subprojects.all().count(), 0) |
| 1068 | + self.assertEqual(self.subproject.superprojects.all().count(), 0) |
| 1069 | + self.project.add_subproject(self.subproject) |
| 1070 | + self.assertEqual(self.project.subprojects.all().count(), 1) |
| 1071 | + self.assertEqual(self.subproject.superprojects.all().count(), 1) |
| 1072 | + |
| 1073 | + self.assertTrue(self.project.versions.first().active) |
| 1074 | + self.assertTrue(self.subproject.versions.first().active) |
| 1075 | + symlink_project(self.project.pk) |
| 1076 | + |
| 1077 | + self.assertFilesystem(filesystem_before) |
| 1078 | + |
| 1079 | + self.client.force_login(self.user) |
| 1080 | + self.client.post( |
| 1081 | + reverse('project_version_detail', |
| 1082 | + kwargs={ |
| 1083 | + 'project_slug': self.subproject.slug, |
| 1084 | + 'version_slug': self.subproject.versions.first().slug, |
| 1085 | + }), |
| 1086 | + data={'privacy_level': 'private', 'active': True}, |
| 1087 | + ) |
| 1088 | + |
| 1089 | + self.assertEqual(self.subproject.versions.first().privacy_level, 'private') |
| 1090 | + self.assertTrue(self.subproject.versions.first().active) |
| 1091 | + |
| 1092 | + self.client.post( |
| 1093 | + reverse('projects_advanced', |
| 1094 | + kwargs={ |
| 1095 | + 'project_slug': self.subproject.slug, |
| 1096 | + }), |
| 1097 | + data={ |
| 1098 | + # Required defaults |
| 1099 | + 'python_interpreter': 'python', |
| 1100 | + 'default_version': 'latest', |
| 1101 | + |
| 1102 | + 'privacy_level': 'private', |
| 1103 | + }, |
| 1104 | + ) |
| 1105 | + |
| 1106 | + self.assertTrue(self.subproject.versions.first().active) |
| 1107 | + self.subproject.refresh_from_db() |
| 1108 | + self.assertEqual(self.subproject.privacy_level, 'private') |
| 1109 | + self.assertFilesystem(filesystem_after) |
0 commit comments