def test_query_seqs_display(self):
        """
        Tests the query sequences display page
        """
        # User visits home page and submits a protein alignment
        self.client.get(self.url)
        csrftoken = self.client.cookies["csrftoken"]
        alignment_string = file_to_string("spa_protein_alignment.fasta")
        self.client.headers.update({"referer": self.url})
        r = self.client.post(
            self.url, data={"csrfmiddlewaretoken": csrftoken, "seq_type": "Protein", "align_input": alignment_string}
        )
        display = html.parse(StringIO(r.text)).getroot()
        title = display.cssselect('title[id="head-title"]')
        sequence_lines = display.cssselect('p[class="query_seq_display"]')
        sequence_meta = display.find_class("query_seq_meta")
        render_form = display.cssselect('form[id="render"]')

        # She is redirected to a page showing the submitted sequences from her alignment and a simple consensus sequence
        self.assertEqual(r.status_code, 200, r.status_code)
        self.assertEqual(
            "%s/query-sequences" % self.url, "/".join(r.url.split("/")[:-2]), "/".join(r.url.split("/")[:-2])
        )
        self.assertEqual("Formalign.eu Sequence Display", title[0].text_content(), title[0].text_content())
        self.assertIsNotNone(sequence_lines, "sequences are empty")
        for l in sequence_lines:
            self.assertTrue(len(l.text_content()) <= 80, l.text_content())
        seqs = file_to_string("spa_protein_alignment_display.txt").splitlines()
        for i, a in enumerate(seqs):
            self.assertEqual(
                a, sequence_lines[i].text_content(), "%s: %s" % (format(i), sequence_lines[i].text_content())
            )
        seqs_meta = file_to_string("spa_protein_alignment_display_meta.txt").splitlines()
        for i, a in enumerate(seqs_meta):
            self.assertEqual(a, sequence_meta[i].text_content(), sequence_meta[i].text_content())

        # She is happy with the result, sees a "Render" button and clicks it.
        self.assertEqual("get", render_form[0].attrib.get("method"), render_form[0].attrib.get("method"))
        self.assertEqual(
            "align-display",
            render_form[0].attrib.get("action").split("/")[1],
            render_form[0].attrib.get("action").split("/")[1],
        )
        slug_pattern = re.compile("^([a-zA-Z]|\d){16}$")
        self.assertTrue(
            re.match(slug_pattern, render_form[0].attrib.get("action").split("/")[-2]),
            render_form[0].attrib.get("action").split("/")[-2],
        )
        r = self.client.get(self.url + render_form[0].attrib.get("action"))

        # She is redirected to the render page
        self.assertEqual(r.status_code, 200, r.status_code)
        align = html.parse(StringIO(r.text)).getroot()
        title = align.cssselect('title[id="head-title"]')
        self.assertEqual("Formalign.eu Alignment Display", title[0].text_content(), title[0].text_content())
        self.assertEqual(
            "%s/align-display" % self.url, "/".join(r.url.split("/")[:-2]), "/".join(r.url.split("/")[:-2])
        )
예제 #2
0
 def setUp(self):
     """
     Creates an alignment from ser_thr_kin_short in the db
     """
     align_input = io.StringIO(file_to_string("ser_thr_kin_short.fasta"))
     data = parse_fasta_alignment(align_input)
     for d in data:
         d.seq.alphabet = Gapped(ExtendedIUPACProtein())
     self.alignment = data
     align_input_a = io.StringIO(file_to_string("protein_annotate_test.fasta"))
     data_a = parse_fasta_alignment(align_input_a)
     for d in data_a:
         d.seq.alphabet = Gapped(ExtendedIUPACProtein())
     self.alignment_a = data_a
예제 #3
0
 def setUp(self):
     name = "A. tha. SPA family protein alignment"
     align_input = io.StringIO(file_to_string("spa_protein_alignment.fasta"))
     data = parse_fasta_alignment(align_input)
     for d in data:
         d.seq.alphabet = Gapped(ExtendedIUPACProtein())
     align = Alignment.objects.create_alignment(name, data)
     self.response_prot = self.client.get("/query-sequences/" + str(align.slug) + "/")
     name = "A. tha. SPA family DNA alignment"
     align_input = io.StringIO(file_to_string("spa_cds_alignment.fasta"))
     data = parse_fasta_alignment(align_input)
     for d in data:
         d.seq.alphabet = Gapped(ExtendedIUPACDNA())
     align = Alignment.objects.create_alignment(name, data)
     self.response_dna = self.client.get("/query-sequences/" + str(align.slug) + "/")
예제 #4
0
    def test_form_validation_returns_correct_seqrecord_alphabet(self):
        """
        Tests that seqrecords get correct alphabet according to user input of seq_type
        :return:
        """
        input_seqs = file_to_string('protein.fasta')
        form = QueryForm(data={'align_input': input_seqs, 'seq_type': 'Protein'})
        self.assertTrue(form.is_valid())
        for f in form.cleaned_data['align_input']:
            self.assertEqual(str(f.seq.alphabet), "Gapped(ExtendedIUPACProtein(), '-')", Gapped(ExtendedIUPACProtein()).letters)

        input_seqs = file_to_string('DNA.fasta')
        form = QueryForm(data={'align_input': input_seqs, 'seq_type': 'DNA'})
        self.assertTrue(form.is_valid())
        for f in form.cleaned_data['align_input']:
            self.assertEqual(str(f.seq.alphabet), "Gapped(ExtendedIUPACDNA(), '-')", Gapped(ExtendedIUPACDNA()).letters)
예제 #5
0
    def test_alignment_display_speeds(self):
        """
        Tests alignment display page
        """
        # User visits home page, submits a protein alignment and renders it
        files = [
            'spa_protein_alignment.fasta',
            'spa1_protein_alignment.fasta',
            'ser_thr_kinase_family.fasta'
        ]
        for f in files:
            q_display = []
            a_display = []
            for i in range(3):
                self.client = requests.Session()
                self.client.get(self.live_server_url)
                csrftoken = self.client.cookies['csrftoken']
                alignment_string = file_to_string(f)
                start = time.time()
                r = self.client.post(self.live_server_url,
                                     data={'csrfmiddlewaretoken': csrftoken, 'seq_type': 'Protein',
                                           'align_input': alignment_string})
                roundtrip1 = time.time() - start
                q_display.append(roundtrip1)

                render_form = html.parse(StringIO(r.text)).getroot().cssselect('form[id="render"]')
                start = time.time()
                r = self.client.get(self.live_server_url + render_form[0].attrib.get('action'))
                roundtrip2 = time.time() - start
                a_display.append(roundtrip2)

                self.client.close()
            self.assertTrue(mean(q_display) <= 2, 'query sequences display of %s took: %s' % (f, mean(q_display)))
            self.assertTrue(mean(a_display) <= 2, 'alignment display of %s took: %s' % (f, mean(a_display)))
예제 #6
0
    def test_align_display_page_displays_correct_consensus(self):
        """
        Tests that align_display displays the correct consensus sequence
        """
        expected_seqs = file_to_string("spa_protein_alignment.fasta")
        align_expected = io.StringIO(expected_seqs)
        alignment = parse_fasta_alignment(align_expected)
        alignment = consensus_add(alignment)

        # get displayed sequences
        with self.assertHTML(self.response, "tr") as elems:
            seq_disp = []
            for els in elems:
                seq_disp_line = []
                for e in els.findall("td")[:-1]:
                    if e.attrib["class"] in ["residue S0", "residue S1"]:
                        seq_disp_line.append(e.text)
                if seq_disp_line:
                    seq_disp.append(seq_disp_line)

        # recompose sequences
        cat_re_seq = []
        for j in range(len(alignment) - 1, len(seq_disp), len(alignment)):
            re_seq = [seq_disp[j] for j in range(len(alignment) - 1, len(seq_disp), len(alignment))]
            cat_re_seq = []
            for r in re_seq:
                cat_re_seq.extend(r)

        # check consensus
        cons_li = list(alignment[-1].seq)
        self.assertEqual(cons_li, cat_re_seq, cat_re_seq)
예제 #7
0
    def test_align_display_page_displays_correct_protein_alignment_sequence(self):
        """
        Tests that align_display displays an alignment with correct sequences
        """
        expected_seqs = file_to_string("spa_protein_alignment.fasta")
        align_expected = io.StringIO(expected_seqs)
        alignment = parse_fasta_alignment(align_expected)

        # get displayed sequences
        with self.assertHTML(self.response, "tr") as elems:
            seq_disp = []
            for els in elems:
                seq_disp_line = []
                for e in els.findall("td")[:-1]:
                    if e.attrib["class"] in ["residue S0", "residue S1"]:
                        seq_disp_line.append(e.text)
                if seq_disp_line:
                    seq_disp.append(seq_disp_line)

        # recompose sequences
        re_seqs = []
        cat_re_seq = []
        for i in range(0, len(alignment) + 1):
            for j in range(i, len(seq_disp), len(alignment) + 1):
                re_seq = [seq_disp[j] for j in range(i, len(seq_disp), len(alignment) + 1)]
                cat_re_seq = []
                for r in re_seq:
                    cat_re_seq.extend(r)
            re_seqs.append(cat_re_seq)

        # check sequences against original alignment
        for i, al in enumerate(alignment):
            al_li = list(al.seq)
            self.assertEqual(al_li, re_seqs[i], re_seqs[i])
예제 #8
0
 def test_redirect_to_seqdisplay_on_post(self):
     """
     Tests that valid POST request on index page redirects to /query-sequences/
     :return:
     """
     input_seqs = file_to_string("spa_protein_alignment.fasta")
     response = self.client.post("/", {"align_input": input_seqs, "seq_type": "Protein"})
     self.assertTrue("/query-sequences/" in response.url)
예제 #9
0
 def setUp(self):
     self.name = 'A. tha. SPA family alignment'
     align_input = io.StringIO(file_to_string('spa_protein_alignment.fasta'))
     self.data = parse_fasta_alignment(align_input)
     alphabet = Gapped(ExtendedIUPACProtein())
     for a in self.data:
         a.seq.alphabet = alphabet
     self.data._alphabet = alphabet
예제 #10
0
 def test_display_page_uses_display_seq_template(self):
     """
     Tests that seq_display view returns a 200 response on a POST request and uses the correct template
     :return:
     """
     input_seqs = file_to_string("protein.fasta")
     response = self.client.post("/", {"align_input": input_seqs, "seq_type": "DNA"})
     self.assertEqual(response.status_code, 200)
예제 #11
0
 def setUp(self):
     name = 'A. tha. SPA family protein alignment'
     align_input = io.StringIO(file_to_string('spa_protein_alignment.fasta'))
     data = parse_fasta_alignment(align_input)
     for d in data:
         d.seq.alphabet = Gapped(ExtendedIUPACProtein())
     align = Alignment.objects.create_alignment(name, data)
     self.slug = align.slug
예제 #12
0
 def test_alignment_is_saved_on_post(self):
     """
     Tests that alignment is saved on valid POST request to index
     :return:
     """
     input_seqs = file_to_string("spa_protein_alignment.fasta")
     response = self.client.post("/", {"align_input": input_seqs, "seq_type": "Protein"})
     slug = re.match(r"^/query-sequences/(?P<align_id>([a-zA-Z]|\d){16})/", response.url).group("align_id")
     pk = Alignment.objects.get(slug=slug).pk
     alignment = Alignment.objects.get_alignment(pk)
     self.assertEqual([seq.id for seq in alignment], ["NP_175717", "NP_683567", "NP_182157", "NP_192849"])
예제 #13
0
 def test_parse_fasta_alignment_returns_expected_object(self):
     """
     tests that parse_fasta_alignment returns the expected object
     """
     align = io.StringIO(file_to_string("ser_thr_kin_short.fasta"))
     parsed = parse_fasta_alignment(align)
     self.assertEqual(
         ["DMD401_1-640", "CER09D1_11-435", "EGFR", "DMDPR2_1-384"],
         [p.description for p in parsed],
         [p.description for p in parsed],
     )
예제 #14
0
 def test_parse_fasta_alignment(self):
     """
     Tests that the parse_fasta function returns expected values with a valid fasta alignment
     :return:
     """
     input_seqs = file_to_string("protein.fasta")
     parsed = parse_fasta_alignment(io.StringIO(input_seqs))
     self.assertEqual(parsed[0].description, "sequence1")
     self.assertEqual(parsed[0].seq, "MKERBGWAQ--QGKKPWRF--EEW")
     self.assertEqual(parsed[1].description, "sequence2")
     self.assertEqual(parsed[1].seq, "MKERBGWA-SYQGKKPWRFAQ-EW")
예제 #15
0
 def setUp(self):
     """
     Creates a response from a GET request to /align-display/ with an alignment pk
     :param input_file: file containing alignment
     :return: response
     """
     name = "SPA1 protein alignment"
     align_input = io.StringIO(file_to_string("spa1_protein_alignment.fasta"))
     data = parse_fasta_alignment(align_input)
     for d in data:
         d.seq.alphabet = Gapped(ExtendedIUPACProtein())
     self.align = Alignment.objects.create_alignment(name, data)
예제 #16
0
 def response_for_invalid_post_request(self, input_file="", seq_type="Protein"):
     """
     Creates a response from a POST request to /query-sequences/ with an invalid alignment
     :param input_file: file containing invalid alignment
     :return: response
     """
     if input_file:
         input_seqs = file_to_string(input_file)
     else:
         input_seqs = ""
     response = self.client.post("/", {"align_input": input_seqs, "seq_type": seq_type})
     return response
예제 #17
0
 def test_display_page_uses_display_seq_template_on_GET(self):
     """
     Tests that seq_display view returns a 200 response on a GET request and uses the correct template
     :return:
     """
     name = "A. tha. SPA family alignment"
     align_input = io.StringIO(file_to_string("spa_protein_alignment.fasta"))
     data = parse_fasta_alignment(align_input)
     for d in data:
         d.seq.alphabet = Gapped(ExtendedIUPACProtein())
     save = Alignment.objects.create_alignment(name, data)
     response = self.client.get("/query-sequences/" + str(save.slug) + "/")
     self.assertEqual(response.status_code, 200)
예제 #18
0
 def setUp(self):
     seq_input = io.StringIO(file_to_string('spa_protein_alignment.fasta'))
     data = SeqIO.parse(seq_input, 'fasta')
     self.seqs = [d.upper() for d in data]
     for s in self.seqs:
         s.seq.alphabet = ExtendedIUPACProtein()
     self.seq = Seqrecord.objects.create(
         seq=str(self.seqs[0].seq),
         alphabet=str(self.seqs[0].seq.alphabet),
         seq_id=str(self.seqs[0].id),
         name=str(self.seqs[0].name),
         description=self.seqs[0].description,
     )
예제 #19
0
 def test_display_page_displays_consensus(self):
     """
     Tests that seq_display displays the consensus sequence on a valid POST request
     :return:
     """
     response = self.response_prot
     with self.assertHTML(response, 'h3[class="query_seq_meta bg-color-body"]') as elems:
         self.assertEqual(elems[-1].text, "consensus 70%:", "consensus meta: " + format(elems[0].text))
     cons_seq = file_to_string("consensus.txt")
     with self.assertHTML(response, 'div[class="query_seq bg-color-body"]') as elems:
         self.assertEqual(
             elems[-1].findall("p")[0].text, cons_seq[:80], "consensus seq: " + elems[-1].findall("p")[0].text
         )
         self.assertNotIn(" ", elems[-1].findall("p")[0].text)
         self.assertNotIn("\n", elems[-1].findall("p")[0].text)
예제 #20
0
 def validation(self, error_text, input_file='', seq_type='Protein'):
     """
     Performs validation test for invalid forms, takes a user alignment input, asserts form.is_valid as false and
     checks the error
     :param:
     :return:
     """
     if input_file:
         input_seqs = file_to_string(input_file)
     else:
         input_seqs = ''
     form = QueryForm(data={'align_input': input_seqs, 'seq_type': seq_type})
     self.assertFalse(form.is_valid())
     self.assertEqual(
         form.errors['align_input'],
         [error_text], format(form.errors['align_input'])
     )
예제 #21
0
    def test_align_display_page_displays_sequences_in_the_correct_order(self):
        """
        Tests that align_display displays the sequences in the correct order
        """
        expected_seqs = file_to_string("spa_protein_alignment.fasta")
        align_expected = io.StringIO(expected_seqs)
        alignment = parse_fasta_alignment(align_expected)
        ids = [al.id for al in alignment]
        ids.append("consensus 70%")
        with self.assertHTML(self.response, "tr") as elems:
            ids_disp = []
            for els in elems:
                for e in els.findall("td")[:-1]:
                    if e.attrib["class"] == "seq_id":
                        ids_disp.append(e.text)

        # blocks of sequence IDs
        bl = [ids_disp[i : i + len(ids)] for i in range(0, len(ids_disp), len(ids))]
        for b in bl:
            for i in range(len(ids)):
                self.assertEqual(ids[i], b[i], b[i])
예제 #22
0
    def alignment_validation(self, seqs):
        # She visits the Formalign.eu site
        r = self.client.get(self.url)
        csrftoken = self.client.cookies['csrftoken']
        index = html.parse(StringIO(r.text)).getroot()
        title = index.cssselect('title[id="head-title"]')
        brand = index.cssselect('a[class="navbar-brand"]')

        # page displays no error message
        self.assertEqual(r.status_code, 200, r.status_code)

        # User sees she's on the right page because she can see the name of the site in the title and the brand.
        self.assertEqual('Formalign.eu Home', title[0].text_content(), title[0].text_content())
        self.assertEqual(self.url + '/', r.url, r.url)
        self.assertEqual('Formalign.eu', brand[0].text_content(), brand[0].text_content())

        for t in ['protein', 'DNA']:
            for s in seqs:
                # she submits the invalid alignment
                alignment_string = file_to_string('%s%s.fasta' % (t, s['seq'])) if s['seq'] else None
                self.client.headers.update({'referer': self.url})
                r = self.client.post(self.url,
                                     data={'csrfmiddlewaretoken': csrftoken, 'seq_type': 'Protein',
                                           'align_input': alignment_string})
                index = html.parse(StringIO(r.text)).getroot()
                title = index.cssselect('title[id="head-title"]')
                brand = index.cssselect('a[class="navbar-brand"]')
                error_text = index.cssselect('ul[class="errorlist"]')[0].cssselect('li')[0].text_content()

                # response contains no error code
                self.assertEqual(r.status_code, 200, r.status_code)

                # user is redirected to the index page
                self.assertEqual('Formalign.eu Home', title[0].text_content(), title[0].text_content())
                self.assertEqual(self.url + '/', r.url, r.url)
                self.assertEqual('Formalign.eu', brand[0].text_content(), brand[0].text_content())

                # error text is displayed on index page
                self.assertEqual(s['error'], error_text, error_text)
예제 #23
0
    def test_align_display_renders_correct_color_classes(self):
        """
        Tests that align_display assigns the correct color classes (residue S0 or residue S1) to the residues
        """
        expected_seqs = file_to_string("spa_protein_alignment.fasta")
        align_expected = io.StringIO(expected_seqs)
        alignment = parse_fasta_alignment(align_expected)
        alignment = consensus_add(alignment)
        alignment = annotate(alignment)

        # get displayed sequences
        with self.assertHTML(self.response, "tr") as elems:
            seq_disp = []
            for els in elems:
                seq_disp_line = []
                for e in els.findall("td")[:-1]:
                    if e.attrib["class"] in ["residue S0", "residue S1"]:
                        seq_disp_line.append(e.attrib["class"])
                if seq_disp_line:
                    seq_disp.append(seq_disp_line)

        # recompose sequences
        re_seqs = []
        cat_re_seq = []
        for i in range(0, len(alignment)):
            for j in range(i, len(seq_disp), len(alignment)):
                re_seq = [seq_disp[j] for j in range(i, len(seq_disp), len(alignment))]
                cat_re_seq = []
                for r in re_seq:
                    cat_re_seq.extend(r)
            re_seqs.append(cat_re_seq)

        # check color classes
        for i, al in enumerate(alignment):
            al_li = ["residue S%s" % a for a in al.letter_annotations["eq"]]
            self.assertEqual(al_li, re_seqs[i], re_seqs)
예제 #24
0
    def invalid_format_sequence(self, file):
        seq_type_button_dict = {'DNA': 'input#id_seq_type_1', 'protein': 'input#id_seq_type_0'}
        test_seq = {'DNA': 'AGTCC-TAAGGTCGCCAATGGGCA', 'protein': 'MKERBGWAQ--QGKKPWRF--EEW'}

        # she visits the Formalign.eu site
        self.browser.get(self.url + '/')

        # She clicks the appropriate button and clears the input field
        seq_type_button = self.browser.find_element_by_css_selector(seq_type_button_dict[file['seq_type']])
        seq_type_button.click()
        self.assertEqual(
                True,
                seq_type_button.is_selected(),
                'button is selected for ' + file['align_format'] + ' ' + file['seq_type'] + ': ' +
                str(seq_type_button.is_selected())
        )
        alignment_input = self.browser.find_element_by_css_selector('textarea#id_align_input')
        alignment_input.clear()

        # She pastes in an invalid fasta alignment
        alignment_string = file_to_string(file['invalid'])
        pyperclip.copy(alignment_string)
        alignment_input.send_keys(Keys.CONTROL, 'v')
        self.browser.find_element_by_id('submit-align').click()

        # Wait for Firefox
        time.sleep(self.wait)

        # Since her FASTA format is invalid she gets redirected to the submission form where she sees an
        # error message telling her that her alignment format is invalid
        self.assertEqual(
                'Formalign.eu Home',
                self.browser.title,
                'browser.title for ' + file['align_format'] + ' ' + file['seq_type'] + ': ' + self.browser.title
        )
        error = self.browser.find_element_by_css_selector('.errorlist').find_element_by_tag_name('li')
        self.assertEqual(
                FORMAT_ERROR,
                error.text,
                'error.text for ' + file['align_format'] + ' ' + file['seq_type'] + ': ' + error.text,

        )

        # she corrects her alignment and submits an invalid clustal alignment
        alignment_input = self.browser.find_element_by_css_selector('textarea#id_align_input')
        alignment_input.clear()
        alignment_string = file_to_string(file['valid'])
        pyperclip.copy(alignment_string)
        alignment_input.send_keys(Keys.CONTROL, 'v')
        self.browser.find_element_by_id('submit-align').click()

        # Wait for Firefox
        time.sleep(self.wait * 10)

        # She got it right this time and is redirected to a page showing the submitted sequences from her alignment
        self.assertEqual(
                'Formalign.eu Sequence Display',
                self.browser.title,
                'browser.title for ' + file['align_format'] + ' ' + file['seq_type'] + ': ' + self.browser.title)
        first_seq_info = self.browser.find_elements_by_css_selector('.query_seq_meta')[0]
        self.assertEqual(
                'sequence1:',
                first_seq_info.text,
                'seq id for ' + file['align_format'] + ' ' + file['seq_type'] + ': ' + first_seq_info.text

        )
        first_seq_content = self.browser.find_elements_by_css_selector('.query_seq_display')[0]
        self.assertIsNotNone(first_seq_content)
        self.assertEqual(
                test_seq[file['seq_type']],
                first_seq_content.text,
                'seq id for ' + file['align_format'] + ' ' + file['seq_type'] + ': ' + first_seq_content.text
        )

        # She wonders whether she can use other formats and decides to navigate back to the home page
        home_button = self.browser.find_element_by_css_selector('.navbar-brand')
        home_button.click()
예제 #25
0
    def alignment_validation(self, **kwargs):
        seq_type_button_dict = {'DNA': 'input#id_seq_type_1', 'protein': 'input#id_seq_type_0'}
        test_seq = {'DNA': 'AGTCC-TAAGGTCGCCAATGGGCA', 'protein': 'MKERBGWAQ--QGKKPWRF--EEW'}

        # User visits the formalign.eu site.
        self.browser.get(self.url + '/')

        # She clicks the DNA button
        seq_type_button = self.browser.find_element_by_css_selector(seq_type_button_dict[kwargs['seq_type']])
        seq_type_button.click()

        # She is so excited that she inadvertently submits the empty input
        self.browser.find_element_by_id('submit-align').click()

        # Wait for Firefox
        time.sleep(self.wait)

        self.assertEqual('Formalign.eu Home', self.browser.title, self.browser.title)
        error = self.browser.find_element_by_css_selector('.errorlist').find_element_by_tag_name('li')
        self.assertEqual(
                EMPTY_ERROR,
                error.text
        )

        # She decides to try it out so she pastes in an alignment and submits
        alignment_input = self.browser.find_element_by_css_selector('textarea#id_align_input')
        alignment_string = file_to_string(kwargs['seq_type'] + '_invalid_fasta.fasta')
        alignment_input.send_keys(alignment_string)
        self.browser.find_element_by_id('submit-align').click()

        # Wait for Firefox
        time.sleep(self.wait)

        # unfortunately her FASTA format is invalid so she gets redirected to the submission form where she sees an
        # error message telling her that her FASTA format is invalid
        self.assertEqual(self.browser.title, 'Formalign.eu Home', self.browser.title)
        error = self.browser.find_element_by_css_selector('.errorlist').find_element_by_tag_name('li')
        self.assertEqual(
                FORMAT_ERROR,
                error.text
        )

        # she corrects her alignment and resubmits
        alignment_string = file_to_string(kwargs['seq_type'] + '_invalid_characters.fasta')
        alignment_input = self.browser.find_element_by_css_selector('textarea#id_align_input')
        alignment_input.clear()
        alignment_input.send_keys(alignment_string)
        self.browser.find_element_by_id('submit-align').click()

        # Wait for Firefox
        time.sleep(self.wait)

        # unfortunately now her sequences contain invalid characters so she gets redirected to the submission form
        # again where she sees an error message telling her that her sequences contain invalid characters
        self.assertEqual(self.browser.title, 'Formalign.eu Home', self.browser.title)
        error = self.browser.find_element_by_css_selector('.errorlist').find_element_by_tag_name('li')
        self.assertEqual(
                CHARACTER_ERROR + 'sequence1',
                error.text

        )

        # she corrects her alignment again and resubmits
        alignment_string = file_to_string(kwargs['seq_type'] + '_too_few_sequences.fasta')
        alignment_input = self.browser.find_element_by_css_selector('textarea#id_align_input')
        alignment_input.clear()
        alignment_input.send_keys(alignment_string)
        self.browser.find_element_by_id('submit-align').click()

        # Wait for Firefox
        time.sleep(self.wait)

        # unfortunately this time she accidentally erased one sequence and is left with only one sequence so she gets
        # redirected to the submission form again where she sees an error message telling her that her alignment is not
        # an alignment since it contains only one sequence
        self.assertEqual('Formalign.eu Home', self.browser.title, self.browser.title)
        error = self.browser.find_element_by_css_selector('.errorlist').find_element_by_tag_name('li')
        self.assertEqual(
                LESS_THAN_TWO_SEQS_ERROR,
                error.text,
        )

        # she adds the missing sequence and resubmits
        alignment_string = file_to_string(kwargs['seq_type'] + '_invalid_alignment.fasta')
        alignment_input = self.browser.find_element_by_css_selector('textarea#id_align_input')
        alignment_input.clear()
        alignment_input.send_keys(alignment_string)
        self.browser.find_element_by_id('submit-align').click()

        # Wait for Firefox
        time.sleep(self.wait)

        # it must be starting to be a bit late since she added some residues to her first sequence so it is longer than
        # the second now so she gets redirected to the submission form again where she sees an error message telling her
        # that her alignment is not an alignment since the sequences do not all have the same length
        self.assertEqual(self.browser.title, 'Formalign.eu Home', self.browser.title)
        error = self.browser.find_element_by_css_selector('.errorlist').find_element_by_tag_name('li')
        self.assertEqual(
                ALIGNMENT_ERROR,
                error.text,
        )

        # She tries one final time and threatens to throw her laptop out of the window if she gets another
        # error message
        alignment_string = file_to_string(kwargs['seq_type'] + '.fasta')
        alignment_input = self.browser.find_element_by_css_selector('textarea#id_align_input')
        alignment_input.clear()
        alignment_input.send_keys(alignment_string)
        self.browser.find_element_by_id('submit-align').click()

        # Wait for Firefox
        time.sleep(self.wait * 5)

        # She got it right this time and is redirected to a page showing the submitted sequences from her alignment
        self.assertEqual(self.browser.title, 'Formalign.eu Sequence Display', self.browser.title)
        first_seq_info = self.browser.find_elements_by_css_selector('.query_seq_meta')[0]
        self.assertEqual(
                'sequence1:',
                first_seq_info.text
        )
        first_seq_content = self.browser.find_elements_by_css_selector('.query_seq_display')[0]
        self.assertIsNotNone(first_seq_content)
        self.assertEqual(first_seq_content.text, test_seq[kwargs['seq_type']])
    def test_basic_user_experience(self):
        """
        Tests basic user interaction with formalign.eu site
        """
        # Lambda user is a biologist who has to make a nice figure containing a multiple alignment for a presentation.
        # She visits the formalign.eu site.
        self.browser.get(self.url + "/")

        # User sees she's on the right page because she can see the name of the site in the heading.
        self.assertEqual(self.browser.title, "Formalign.eu Home", self.browser.title)
        brand_element = self.browser.find_element_by_css_selector(".navbar-brand")
        self.assertEqual("Formalign.eu", brand_element.text)

        # She sees a form that says 'Paste in your alignment in FASTA format:'
        alignment_input = self.browser.find_element_by_css_selector("textarea#id_align_input")
        self.assertIsNotNone(self.browser.find_element_by_css_selector('label[for="id_align_input"]'))
        self.assertEqual(
            "Alignment (FASTA, clustalw, stockholm or phylip)", alignment_input.get_attribute("placeholder")
        )

        # She sees two radio buttons for DNA and protein
        dna_button = self.browser.find_element_by_css_selector("input#id_seq_type_1")
        self.assertIsNotNone(dna_button)
        protein_button = self.browser.find_element_by_css_selector("input#id_seq_type_0")
        self.assertIsNotNone(protein_button)

        # She sees that the DNA button is selected by default
        self.assertEqual(dna_button.is_selected(), True)

        # She clicks the Protein radio button and sees that it gets selected and the DNA button gets unselected
        protein_button.click()
        # Wait for Firefox
        time.sleep(self.sleep)

        self.assertEqual(protein_button.is_selected(), True)
        self.assertEqual(dna_button.is_selected(), False)

        # She pastes in a protein alignment to see what happens
        alignment_string = file_to_string("spa_protein_alignment.fasta")
        pyperclip.copy(alignment_string)
        alignment_input = self.browser.find_element_by_css_selector("textarea#id_align_input")
        alignment_input.send_keys(Keys.CONTROL, "v")
        self.browser.find_element_by_id("submit-align").click()
        # Wait for Firefox
        time.sleep(self.sleep * 5)

        # She is redirected to a page showing the submitted sequences from her alignment and a simple consensus sequence
        self.assertEqual(self.browser.title, "Formalign.eu Sequence Display", self.browser.title)
        seq_content = self.browser.find_elements_by_css_selector(".query_seq_display")
        self.assertIsNotNone(seq_content)
        for f in seq_content:
            self.assertTrue(len(f.text) <= 80)

        first_seq_info = self.browser.find_elements_by_css_selector(".query_seq_meta")[0]
        self.assertEqual(
            "NP_175717 NP_175717.1 SPA1-related 4 protein [Arabidopsis thaliana].:",
            first_seq_info.text,
            first_seq_info.text,
        )
        first_seq_content = self.browser.find_elements_by_css_selector(".query_seq_display")[0]
        self.assertIsNotNone(first_seq_content)
        self.assertEqual(first_seq_content.text, "-" * 80)

        consensus_seq = self.browser.find_elements_by_xpath('//div[@class="query_seq bg-color-body"]')[
            -1
        ].find_elements_by_xpath('./p[@class="query_seq_display"]')[0]
        self.assertIsNotNone(consensus_seq)
        cons_seq = file_to_string("consensus.txt")
        self.assertEqual(consensus_seq.text, cons_seq[:80])
        consensus_meta = self.browser.find_elements_by_xpath('//h3[@class="query_seq_meta bg-color-body"]')[-1]
        self.assertEqual(consensus_meta.text, "consensus 70%:")

        # She is happy with the result, sees a "Render" button and clicks it.
        render_button = self.browser.find_element_by_css_selector("button#render-align")
        self.assertIsNotNone(render_button)
        render_button.click()
        # Wait for Firefox
        time.sleep(self.sleep * 10)

        # She is redirected to the alignment display page
        self.assertEqual("Formalign.eu Alignment Display", self.browser.title, self.browser.title)

        # She sees the alignment displayed with 80 characters per line in blocks of 10 with sequence ids
        s0 = self.browser.find_elements_by_xpath('//tr[@class="al_ln"]')[10].find_elements_by_xpath(
            './td[@class="residue S0"]'
        )
        s1 = self.browser.find_elements_by_xpath('//tr[@class="al_ln"]')[10].find_elements_by_xpath(
            './td[@class="residue S1"]'
        )
        self.assertEqual(len(s0) + len(s1), 80)
        sep = self.browser.find_elements_by_xpath('//tr[@class="al_ln"]')[10].find_elements_by_xpath(
            './td[@class="block_sep"]'
        )
        self.assertEqual(len(sep), 8)

        # She is quite happy with the result and decides to try with another alignment so she navigates back to the
        # home page
        home_button = self.browser.find_element_by_css_selector(".navbar-brand")
        home_button.click()
        # Wait for Firefox
        time.sleep(self.sleep * 2)

        self.assertEqual("Formalign.eu Home", self.browser.title, self.browser.title)

        # She wants to upload a protein stockholm alignment this time from a file
        # She clicks the Protein radio button and sees that it gets selected and the DNA button gets unselected
        protein_button = self.browser.find_element_by_css_selector("input#id_seq_type_0")
        protein_button.click()
        self.assertEqual(protein_button.is_selected(), True)
    def test_index(self):
        """
        Tests index page
        """
        # Lambda user is a biologist who has to make a nice figure containing a multiple alignment for a presentation.
        # She visits the formalign.eu site.
        r = self.client.get(self.url)
        csrftoken = self.client.cookies["csrftoken"]
        index = html.parse(StringIO(r.text)).getroot()
        title = index.cssselect('title[id="head-title"]')
        brand = index.cssselect('a[class="navbar-brand"]')
        align_input = index.cssselect('textarea[id="id_align_input"]')
        prot_button = index.cssselect('input[id="id_seq_type_0"]')
        prot_button_label = index.cssselect('label[for="id_seq_type_0"]')
        dna_button = index.cssselect('input[id="id_seq_type_1"]')

        # page displays no error message
        self.assertEqual(r.status_code, 200, r.status_code)

        # User sees she's on the right page because she can see the name of the site in the title and the brand.
        self.assertEqual("Formalign.eu Home", title[0].text_content(), title[0].text_content())
        self.assertEqual(self.url + "/", r.url, r.url)
        self.assertEqual("Formalign.eu", brand[0].text_content(), brand[0].text_content())

        # She sees a form that says 'Paste in your alignment'
        self.assertEqual(
            "Paste in your alignment:(FASTA, clustalw, stockholm or phylip)",
            align_input[0].label.text_content(),
            align_input[0].label.text_content(),
        )

        # There's a textarea with a placeholder saying 'Alignment'
        self.assertEqual(
            "Alignment (FASTA, clustalw, stockholm or phylip)",
            align_input[0].attrib.get("placeholder"),
            align_input[0].attrib.get("placeholder"),
        )

        # She sees two radio buttons for DNA and protein
        self.assertEqual(
            "Input sequence type:", prot_button[0].label.text_content(), prot_button[0].label.text_content()
        )
        self.assertEqual(" Protein", prot_button_label[1].text_content(), prot_button_label[1].text_content())
        self.assertEqual(" DNA", dna_button[0].label.text_content(), dna_button[0].label.text_content())

        # She sees that the DNA button is selected by default
        self.assertTrue(prot_button[0].checkable, "Protein button is not checkable")
        self.assertFalse(prot_button[0].checked, "Protein button was checked by default")
        self.assertTrue(dna_button[0].checkable, "DNA button is not checkable")
        self.assertTrue(dna_button[0].checked, "DNA button was not checked by default")

        # She checks the protein button and pastes in an alignment
        alignment_string = file_to_string("spa_protein_alignment.fasta")
        self.client.headers.update({"referer": self.url})
        r = self.client.post(
            self.url, data={"csrfmiddlewaretoken": csrftoken, "seq_type": "Protein", "align_input": alignment_string}
        )
        self.assertEqual(r.status_code, 200, r.status_code)
        display = html.parse(StringIO(r.text)).getroot()
        title = display.cssselect('title[id="head-title"]')

        # She is redirected to the sequence display page
        self.assertEqual("Formalign.eu Sequence Display", title[0].text_content(), title[0].text_content())
        self.assertEqual(
            "%s/query-sequences" % self.url, "/".join(r.url.split("/")[:-2]), "/".join(r.url.split("/")[:-2])
        )
    def test_alignment_display(self):
        """
        Tests alignment display page
        """
        # User visits home page, submits a protein alignment and renders it
        self.client.get(self.url)
        csrftoken = self.client.cookies["csrftoken"]
        alignment_string = file_to_string("spa_protein_alignment.fasta")
        self.client.headers.update({"referer": self.url})
        r = self.client.post(
            self.url, data={"csrfmiddlewaretoken": csrftoken, "seq_type": "Protein", "align_input": alignment_string}
        )
        render_form = html.parse(StringIO(r.text)).getroot().cssselect('form[id="render"]')
        r = self.client.get(self.url + render_form[0].attrib.get("action"))

        # She is redirected to the render page
        self.assertEqual(r.status_code, 200, r.status_code)
        align = html.parse(StringIO(r.text)).getroot()
        title = align.cssselect('title[id="head-title"]')
        self.assertEqual("Formalign.eu Alignment Display", title[0].text_content(), title[0].text_content())
        self.assertEqual(
            "%s/align-display" % self.url, "/".join(r.url.split("/")[:-2]), "/".join(r.url.split("/")[:-2])
        )

        # She sees the alignment displayed with 80 characters per line in blocks of 10 with sequence ids
        seqs_meta = file_to_string("spa_protein_alignment_meta.txt").splitlines()
        tables = align.find_class("align_table")
        for nr, t in enumerate(tables):
            lines = t.find_class("al_ln")
            for i, l in enumerate(lines):
                self.assertEqual("seq_id", l[0].attrib.get("class"), l[0].attrib.get("class"))
                self.assertEqual(seqs_meta[i], l[0].text_content(), l[0].text_content())
                self.assertTrue(len(l[1:]) <= 89, len(l[1:]))
                self.assertEqual("display_artifact", l[-1].attrib.get("class"))
                if len(l[1:]) >= 12:
                    for j in range(1, len(l[1:]) % 10):
                        self.assertEqual(
                            "block_sep",
                            l[j * 11].attrib.get("class"),
                            "line number: %s, line length: %s, column: %s" % (i, len(l), format(j * 11)),
                        )

                        for k in range(j * 11 - 10, j * 11):
                            self.assertTrue(
                                l[k].attrib.get("class") == "residue S0" or l[k].attrib.get("class") == "residue S1",
                                "class: %s, table: %s, line: %s, column: %s" % (l[k].attrib.get("class"), nr, i, k),
                            )
                else:
                    for j in range(1, len(l)):
                        self.assertTrue(
                            l[j].attrib.get("class") == "residue S0" or l[j].attrib.get("class") == "residue S1",
                            l[j].attrib.get("class"),
                        )

        # She is quite happy with the result and decides to navigate back to the home page
        home_button = align.cssselect('a[class="navbar-brand"]')
        self.assertEqual("Formalign.eu", home_button[0].text_content(), home_button[0].text_content())
        r = self.client.get(self.url + home_button[0].attrib.get("href"))
        self.assertEqual(r.status_code, 200, r.status_code)
        home = html.parse(StringIO(r.text)).getroot()
        title = home.cssselect('title[id="head-title"]')

        # User sees she's that she's back on the home page she can see the name of the site in the title and the brand.
        self.assertEqual("Formalign.eu Home", title[0].text_content(), title[0].text_content())
        self.assertEqual(self.url + "/", r.url, r.url)