diff --git a/core/views/subscription.py b/core/views/subscription.py index 88dc1d3e3ef1b7557a98ec4df83cdffc695b4cbf..266bd64715e4fbb6d98abfbc0cdbb128869f31fe 100644 --- a/core/views/subscription.py +++ b/core/views/subscription.py @@ -97,7 +97,7 @@ class AssignmentApiViewSet( @permission_classes((IsReviewer,)) def list(self, *args, **kwargs): - super().list(*args, **kwargs) + return super().list(*args, **kwargs) @action(detail=False, permission_classes=(IsReviewer,), methods=['get', 'delete']) def active(self, request): diff --git a/frontend/src/components/AutoLogout.vue b/frontend/src/components/AutoLogout.vue index fd45c04d236d08c13e0b5d3c2beef9e31840c8f8..44af14346071d15598117856ed3d94dc6151269b 100644 --- a/frontend/src/components/AutoLogout.vue +++ b/frontend/src/components/AutoLogout.vue @@ -15,10 +15,12 @@ </v-card-text> <v-card-actions> <v-btn flat color="grey lighten-0" + id="logout-btn" @click="logout" >Logout now</v-btn> <v-spacer/> <v-btn flat color="blue darken-2" + id="continue-btn" @click="continueWork" >Continue</v-btn> </v-card-actions> diff --git a/frontend/src/store/modules/submission-notes.ts b/frontend/src/store/modules/submission-notes.ts index 3c1f13762c7a7fee8b69b5fd0621b8e182b9de79..b34a6d71024c4e4b30d592d346d27301f7c4c624 100644 --- a/frontend/src/store/modules/submission-notes.ts +++ b/frontend/src/store/modules/submission-notes.ts @@ -1,14 +1,10 @@ import Vue from 'vue' import * as hljs from 'highlight.js' import * as api from '@/api' -import { nameSpacer } from '@/util/helpers' -import { Feedback, FeedbackComment, Submission, SubmissionNoType } from '@/models' +import { Feedback, FeedbackComment, SubmissionNoType } from '@/models' import { RootState } from '@/store/store' -import { Module } from 'vuex' import { getStoreBuilder, BareActionContext } from 'vuex-typex' -export const subNotesNamespace = nameSpacer('submissionNotes/') - export interface SubmissionNotesState { submission: SubmissionNoType ui: { @@ -143,13 +139,16 @@ async function submitFeedback ({ state }: BareActionContext<SubmissionNotesState } if (state.origFeedback.score === undefined && state.updatedFeedback.score === undefined) { throw new Error('You need to give a score.') + } else if (state.updatedFeedback.score !== undefined) { + feedback.score = state.updatedFeedback.score } else { - feedback.score = state.updatedFeedback.score || state.origFeedback.score || 0 + feedback.score = state.origFeedback.score } + if (Object.keys(state.updatedFeedback.feedbackLines || {}).length > 0) { feedback.feedbackLines = state.updatedFeedback.feedbackLines } else if (feedback.score! < SubmissionNotes.submissionType.fullScore!) { - throw new Error('You need to provide a reason for a reduced score.') + throw new Error('You need to add or change a comment when setting a non full score.') } // delete those comments that have been marked for deletion await SubmissionNotes.deleteComments() diff --git a/frontend/tests/unit/autologout.spec.ts b/frontend/tests/unit/components/AutoLogout.spec.ts similarity index 51% rename from frontend/tests/unit/autologout.spec.ts rename to frontend/tests/unit/components/AutoLogout.spec.ts index 823f4815a87243b31368b05a6cf19254a77dff16..e33bdabf95928c2c241cfdc798d2b81351aae7a2 100644 --- a/frontend/tests/unit/autologout.spec.ts +++ b/frontend/tests/unit/components/AutoLogout.spec.ts @@ -1,33 +1,58 @@ import Vuex from 'vuex' import { mount, createLocalVue } from '@vue/test-utils' -import AutoLogout from '../../src/components/AutoLogout.vue' +import AutoLogout from '@/components/AutoLogout.vue' import sinon from 'sinon' import { Authentication } from '@/store/modules/authentication' - import chai from 'chai' chai.should() -const localVue = createLocalVue(); -localVue.use(Vuex); +let localVue = createLocalVue() +localVue.use(Vuex) describe('Auto Logout Unit Tests', () => { + let store: any = null + let consoleTemp = { + warn: console.warn, + error: console.error, + } + + before(function() { + console.warn = function() {} + console.error = function() {} + }) + + after(function() { + console.warn = consoleTemp.warn + console.error = consoleTemp.error + }) + + beforeEach(() => { + store = new Vuex.Store({ + state: {} + }) + }) + afterEach(() => { sinon.restore() }) - + it('should be hidden by default', () => { - let store = new Vuex.Store({}) - let wrapper = mount(AutoLogout, { localVue, store }) + let wrapper = mount(AutoLogout, { localVue: localVue, store }) wrapper.vm.$data.logoutDialog.should.equal(false) wrapper.html().should.contain('<div class="v-dialog v-dialog--persistent" style="max-width: 30%; display: none;">') }) it('should be visible when logoutDialog is set to true', () => { + let store = new Vuex.Store({}) + let wrapper = mount(AutoLogout, { localVue, store }) + wrapper.vm.$data.logoutDialog = true + wrapper.html().should.contain('<div class="v-dialog v-dialog--active v-dialog--persistent" style="max-width: 30%;">') + }) + it('should get a refresh token from the server when user clicks button', () => { let spy = sinon.spy() sinon.replace(Authentication, 'refreshJWT', spy) let store = new Vuex.Store({}) let wrapper = mount(AutoLogout, { localVue, store }) - // @ts-ignore - wrapper.vm.continueWork() + wrapper.find('#continue-btn').trigger('click') spy.called.should.equal(true) }) }) diff --git a/functional_tests/test_export_modal.py b/functional_tests/test_export_modal.py index 346842dcd377498f7be2eda3d3d61204d7b40b55..fbc3b130b1ab14afd9704ef7a157591d59027eba 100644 --- a/functional_tests/test_export_modal.py +++ b/functional_tests/test_export_modal.py @@ -93,8 +93,9 @@ class ExportTestModal(LiveServerTestCase): export_type_json = data_export_modal.find_element_by_xpath("//*[contains(text(), 'JSON')]") export_type_json.click() data_export_btn = data_export_modal.find_element_by_id('export-data-download-btn') + before_click_handles = self.browser.window_handles data_export_btn.click() - WebDriverWait(self.browser, 10).until(ec.new_window_is_opened) + WebDriverWait(self.browser, 10).until(ec.new_window_is_opened(before_click_handles)) tabs = self.browser.window_handles self.assertEqual(2, len(tabs)) self.browser.switch_to.window(tabs[1]) @@ -109,9 +110,10 @@ class ExportTestModal(LiveServerTestCase): export_instance.click() instance_export_modal = self.browser.find_element_by_id('instance-export-modal') instance_export_btn = instance_export_modal.find_element_by_id('instance-export-dl') + before_click_handles = self.browser.window_handles instance_export_btn.click() - WebDriverWait(self.browser, 10).until(ec.new_window_is_opened) - tabs = self.browser.window_handles - self.assertEqual(2, len(tabs)) - self.browser.switch_to.window(tabs[1]) + WebDriverWait(self.browser, 10).until(ec.new_window_is_opened(before_click_handles)) + after_click_handles = self.browser.window_handles + self.assertEqual(2, len(after_click_handles)) + self.browser.switch_to.window(after_click_handles[1]) self.assertIn('B.Inf.4242 Test Module', self.browser.find_element_by_tag_name('body').text) diff --git a/functional_tests/test_front_pages.py b/functional_tests/test_front_pages.py index ec38b58f6b9d82e87ab7f208803e5cdde39027d6..adcc09757bf23ad1739c9a02bc30c34e74ad4f15 100644 --- a/functional_tests/test_front_pages.py +++ b/functional_tests/test_front_pages.py @@ -1,7 +1,6 @@ -import time - from django.test import LiveServerTestCase from selenium import webdriver +from selenium.webdriver.support.ui import WebDriverWait from core import models from core.models import UserAccount @@ -47,11 +46,20 @@ class UntestedParent: self.assertEqual('Statistics', title.text) def test_available_tasks_are_shown(self): + # A function that takes the element corresponding to the tasks + # component and return a function that can be used as a condition for + # WebDriverWait + def subscriptions_loaded_cond(tasks_el): + def loaded(*args): + sub_links = tasks_el.find_elements_by_tag_name('a') + return any((link != '/home' for link in extract_hrefs_hashes(sub_links))) + return loaded + self._login() tasks = self.browser.find_element_by_name('subscription-list') title = tasks.find_element_by_class_name('v-toolbar__title') self.assertEqual('Tasks', title.text) - time.sleep(8) + WebDriverWait(self.browser, 6).until(subscriptions_loaded_cond(tasks)) subscription_links = extract_hrefs_hashes( tasks.find_elements_by_tag_name('a') ) diff --git a/functional_tests/test_login_page.py b/functional_tests/test_login_page.py index 0849f0c61175a87e37d607a35603bcb23b32d7eb..23d5fb5d4533e945e0295634bd13229fed877515 100644 --- a/functional_tests/test_login_page.py +++ b/functional_tests/test_login_page.py @@ -1,8 +1,8 @@ -import time - from django.test import LiveServerTestCase from selenium import webdriver from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support import expected_conditions as ec +from selenium.webdriver.support.ui import WebDriverWait from core.models import UserAccount from util.factories import make_test_data @@ -102,7 +102,7 @@ class LoginPageTest(LiveServerTestCase): password_input = self.browser.find_element_by_xpath('//input[@aria-label="Password"]') password_input.send_keys('p') self.browser.find_element_by_xpath('//button[@type="submit"]').send_keys(Keys.ENTER) - time.sleep(1) + WebDriverWait(self.browser, 3).until(ec.url_contains('/home')) def test_tutor_can_login(self): tutor = self.test_data['tutors'][0] @@ -130,8 +130,9 @@ class LoginPageTest(LiveServerTestCase): username_input.send_keys(username) password_input = self.browser.find_element_by_id('input-register-password') password_input.send_keys(password) - self.browser.find_element_by_id('register-submit').click() - time.sleep(1) + register_submit_el = self.browser.find_element_by_id('register-submit') + register_submit_el.click() + WebDriverWait(self.browser, 3).until_not(ec.visibility_of(register_submit_el)) tutor = UserAccount.objects.get(username=username) self.assertEqual(UserAccount.TUTOR, tutor.role) self.assertFalse(tutor.is_active, "Tutors should be inactive after registered") diff --git a/functional_tests/util.py b/functional_tests/util.py index 1333e36aad3e41cdd6269fdaae6dcd09c5c2d192..912d68a388280088cf181b5dabdf55b2b0f9b0e0 100644 --- a/functional_tests/util.py +++ b/functional_tests/util.py @@ -6,6 +6,8 @@ from selenium import webdriver from selenium.webdriver.common.keys import Keys from selenium.webdriver.firefox.options import Options from selenium.webdriver.remote.webelement import WebElement +from selenium.webdriver.support import expected_conditions as ec +from selenium.webdriver.support.ui import WebDriverWait def create_browser() -> webdriver.Firefox: @@ -23,6 +25,7 @@ def login(browser, live_server_url, username, password='p'): password_input = browser.find_element_by_xpath('//input[@aria-label="Password"]') password_input.send_keys(password) browser.find_element_by_xpath('//button[@type="submit"]').send_keys(Keys.ENTER) + WebDriverWait(browser, 3).until(ec.url_contains('/home')) def reset_browser_after_test(browser: webdriver.Firefox, live_server_url): diff --git a/grady/settings/test.py b/grady/settings/test.py index d8eca64b83f3171d73d5e9adf613d14aaa62b7b3..20e0800134edce6a5b16d3079e645333282efdda 100644 --- a/grady/settings/test.py +++ b/grady/settings/test.py @@ -1,4 +1,5 @@ from .default import * +from .instance import * from .live import * REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon'] = '1000/minute'