def test_add_new_charge():
    record, questions = RecordCreator.build_record(
        search("single_case_two_charges"),
        "username",
        "password",
        (),
        {
            "X0001": {
                "summary": {
                    "edit_status": "UPDATE"
                },
                "charges": {
                    "X0001-3": {
                        "edit_status": "ADD",
                        "charge_type": "MisdemeanorClassA",
                        "level": "Misdemeanor Class A",
                        "date": "1/1/2001",
                        "disposition": {
                            "date": "2/1/2020",
                            "ruling": "Convicted"
                        },
                    }
                },
            }
        },
        date.today(),
        LRUCache(4),
    )
    assert isinstance(record.cases[0].charges[2].charge_type,
                      MisdemeanorClassA)
    assert record.cases[0].charges[2].date == date(2001, 1, 1)
    assert record.cases[0].charges[2].edit_status == EditStatus.ADD
def test_add_disposition():
    record, questions = RecordCreator.build_record(
        search("single_case_two_charges"),
        "username",
        "password",
        (),
        {
            "X0001": {
                "summary": {
                    "edit_status": "UPDATE"
                },
                "charges": {
                    "X0001-2": {
                        "disposition": {
                            "date": "1/1/2001",
                            "ruling": "Convicted"
                        },
                        "level": "Misdemeanor Class A",
                    }
                },
            }
        },
        date.today(),
        LRUCache(4),
    )
    assert record.cases[0].charges[
        1].disposition.status == DispositionStatus.CONVICTED
    assert record.cases[0].charges[1].edit_status == EditStatus.UNCHANGED
def test_edit_charge_type_of_charge():
    record, questions = RecordCreator.build_record(
        search("single_case_two_charges"),
        "username",
        "password",
        (),
        {
            "X0001": {
                "summary": {
                    "edit_status": "UPDATE"
                },
                "charges": {
                    "X0001-2": {
                        "edit_status": "UPDATE",
                        "charge_type": "MisdemeanorClassA",
                        "level": "Misdemeanor Class A",
                    }
                },
            }
        },
        date.today(),
        LRUCache(4),
    )
    assert isinstance(record.cases[0].charges[1].charge_type,
                      MisdemeanorClassA)
Exemple #4
0
class Demo(MethodView):
    search_cache = LRUCache(4)

    def post(self):
        record_summary = self.build_response()
        response_data = {"record": record_summary}
        return json.dumps(response_data, cls=ExpungeModelEncoder)

    def build_response(self):
        request_data = request.get_json()
        Search._validate_request(request_data)
        today = Search._build_today(request_data.get("today", ""))
        return Demo._build_record_summary(request_data["aliases"],
                                          request_data.get("questions"),
                                          request_data.get("edits", {}), today)

    @staticmethod
    def _build_record_summary(aliases_data, questions_data, edits_data, today):
        aliases = [
            from_dict(data_class=Alias, data=alias) for alias in aliases_data
        ]
        record, questions = RecordCreator.build_record(
            DemoRecords.build_search_results,
            "username",
            "password",
            tuple(aliases),
            edits_data,
            today,
            Demo.search_cache,
        )
        if questions_data:
            questions = Search._build_questions(questions_data)
        return RecordSummarizer.summarize(record, questions)
def test_edit_some_fields_on_case():
    record, questions = RecordCreator.build_record(
        search("two_cases_two_charges_each"),
        "username",
        "password",
        (),
        {
            "X0002": {
                "summary": {
                    "edit_status": "UPDATE",
                    "location": "ocean",
                    "balance_due": "100",
                    "date": "1/1/1981",
                }
            }
        },
        date.today(),
        LRUCache(4),
    )
    assert len(record.cases) == 2
    assert record.cases[0].summary.location == "earth"
    assert record.cases[0].summary.edit_status == EditStatus.UNCHANGED
    assert record.cases[1].summary.location == "ocean"
    assert record.cases[1].summary.balance_due_in_cents == 10000
    assert record.cases[1].summary.date == date(1981, 1, 1)
    assert record.cases[1].summary.edit_status == EditStatus.UPDATE
    def create_ambiguous_record_with_questions(
        record=JohnDoe.RECORD_WITH_CLOSED_CASES,
        cases={
            "X0001": CaseDetails.case_x(),
            "X0002": CaseDetails.case_x(),
            "X0003": CaseDetails.case_x()
        },
    ) -> Tuple[Record, Dict[str, QuestionSummary]]:
        base_url = "https://publicaccess.courts.oregon.gov/PublicAccessLogin/"
        with requests_mock.Mocker() as m:
            m.post(URL.login_url(), text=PostLoginPage.POST_LOGIN_PAGE)

            m.post("{}{}".format(base_url, "Search.aspx?ID=100"),
                   [{
                       "text": SearchPageResponse.RESPONSE
                   }, {
                       "text": record
                   }])

            for key, value in cases.items():
                m.get("{}{}{}".format(base_url, "CaseDetail.aspx?CaseID=",
                                      key),
                      text=value)

            aliases = (Alias(first_name="John",
                             last_name="Doe",
                             middle_name="",
                             birth_date=""), )
            return RecordCreator.build_record(
                RecordCreator.build_search_results, "username", "password",
                aliases, {}, date_class.today(), LRUCache(4))
def test_update_case_with_add_and_update_and_delete_charges():
    record, questions = RecordCreator.build_record(
        search("single_case_two_charges"),
        "username",
        "password",
        (),
        {
            "X0001": {
                "summary": {
                    "case_number": "X0001",
                    "edit_status": "UPDATE",
                    "location": "ocean",
                    "balance_due": "100",
                    "date": "1/1/1981",
                },
                "charges": {
                    "X0001-1": {
                        "edit_status": "UPDATE",
                        "charge_type": "FelonyClassB",
                        "level": "Felony Class B",
                        "date": "1/1/2001",
                        "disposition": {
                            "date": "2/1/2020",
                            "ruling": "Convicted"
                        },
                    },
                    "X0001-2": {
                        "edit_status": "DELETE"
                    },
                    "X0001-3": {
                        "edit_status": "ADD",
                        "charge_type": "FelonyClassC",
                        "date": "1/1/1900",
                        "level": "Felony Class A",
                        "disposition": {
                            "date": "2/1/1910",
                            "ruling": "Convicted"
                        },
                    },
                },
            }
        },
        date.today(),
        LRUCache(4),
    )
    assert len(record.cases) == 1
    assert record.cases[0].summary.location == "ocean"
    assert record.cases[0].summary.edit_status == EditStatus.UPDATE
    assert record.cases[0].charges[0].ambiguous_charge_id == "X0001-1"
    assert record.cases[0].charges[0].edit_status == EditStatus.UPDATE
    assert isinstance(record.cases[0].charges[0].charge_type, FelonyClassB)
    assert record.cases[0].charges[1].ambiguous_charge_id == "X0001-2"
    assert record.cases[0].charges[1].edit_status == EditStatus.DELETE
    assert record.cases[0].charges[2].ambiguous_charge_id == "X0001-3"
    assert record.cases[0].charges[2].edit_status == EditStatus.ADD
def test_no_op():
    record, questions = RecordCreator.build_record(
        search("two_cases_two_charges_each"), "username", "password", (), {},
        date.today(), LRUCache(4))
    assert len(record.cases) == 2
    assert len(record.cases[0].charges) == 2
    assert record.cases[1].charges[1].ambiguous_charge_id == "X0002-2"
    assert record.cases[1].charges[
        1].disposition.status == DispositionStatus.UNKNOWN
    assert record.cases[1].summary.edit_status == EditStatus.UNCHANGED
    assert isinstance(record.cases[1].charges[0].charge_type, FelonyClassB)
    assert (record.cases[1].charges[0].expungement_result.charge_eligibility.
            status == ChargeEligibilityStatus.ELIGIBLE_NOW)
    assert record.cases[1].charges[
        0].expungement_result.time_eligibility.status == EligibilityStatus.ELIGIBLE
Exemple #9
0
class Search(MethodView):
    search_cache = LRUCache(4)

    def post(self):
        record_summary = self.build_response()
        response_data = {"record": record_summary}
        return json.dumps(response_data, cls=ExpungeModelEncoder, sort_keys=False)

    def build_response(self) -> RecordSummary:
        request_data = request.get_json()
        Search._validate_request(request_data)
        username, password = Search._oeci_login_params(request)
        return Search._build_record_summary(
            username, password, request_data["aliases"], request_data.get("questions"), request_data.get("edits", {})
        )

    @staticmethod
    def _build_record_summary(username, password, aliases_data, questions_data, edits_data) -> RecordSummary:
        aliases = [from_dict(data_class=Alias, data=alias) for alias in aliases_data]
        record, questions = RecordCreator.build_record(
            RecordCreator.build_search_results, username, password, tuple(aliases), edits_data, Search.search_cache
        )
        if questions_data:
            questions = Search._build_questions(questions_data)
        return RecordSummarizer.summarize(record, questions)

    @staticmethod
    def _build_questions(questions_data):
        questions_as_list = [
            from_dict(data_class=QuestionSummary, data=question) for id, question in questions_data.items()
        ]
        return dict(list(map(lambda q: (q.ambiguous_charge_id, q), questions_as_list)))

    @staticmethod
    def _oeci_login_params(request):
        cipher = DataCipher(key=current_app.config.get("SECRET_KEY"))
        if not "oeci_token" in request.cookies.keys():
            error(401, "Missing login credentials to OECI.")
        decrypted_credentials = cipher.decrypt(request.cookies["oeci_token"])
        return decrypted_credentials["oeci_username"], decrypted_credentials["oeci_password"]

    @staticmethod
    def _validate_request(request_data):
        check_data_fields(request_data, ["aliases"])
        for alias in request_data["aliases"]:
            check_data_fields(alias, ["first_name", "last_name", "middle_name", "birth_date"])
def test_add_case():
    record, questions = RecordCreator.build_record(
        search("single_case_two_charges"),
        "username",
        "password",
        (),
        {
            "5": {
                "summary": {
                    "case_number": "5",
                    "edit_status": "ADD",
                    "location": "ocean",
                    "balance_due": "100",
                    "date": "1/1/1981",
                },
                "charges": {
                    "5-1": {
                        "edit_status": "ADD",
                        "charge_type": "FelonyClassC",
                        "level": "Felony Class C",
                        "date": "1/1/2001",
                        "disposition": {
                            "date": "2/1/2020",
                            "ruling": "Convicted"
                        },
                    }
                },
            }
        },
        date.today(),
        LRUCache(4),
    )
    assert len(record.cases) == 2
    assert record.cases[0].summary.location == "earth"
    assert record.cases[0].summary.edit_status == EditStatus.UNCHANGED

    assert record.cases[1].summary.location == "ocean"
    assert record.cases[1].summary.balance_due_in_cents == 10000
    assert record.cases[1].summary.date == date(1981, 1, 1)
    assert record.cases[1].summary.edit_status == EditStatus.ADD
    assert record.cases[1].charges[0].edit_status == EditStatus.ADD
    assert record.cases[1].charges[0].charge_type
    assert isinstance(record.cases[1].charges[0].charge_type, FelonyClassC)
def test_delete_case():
    record, questions = RecordCreator.build_record(
        search("two_cases_two_charges_each"),
        "username",
        "password",
        (),
        {"X0001": {
            "summary": {
                "edit_status": "DELETE"
            }
        }},
        LRUCache(4),
    )

    assert record.cases[0].summary.case_number == "X0001"
    assert record.cases[0].summary.edit_status == EditStatus.DELETE
    assert len(record.cases[0].charges) == 2
    assert record.cases[0].charges[0].edit_status == EditStatus.DELETE
    assert record.cases[0].charges[1].edit_status == EditStatus.DELETE
    assert record.cases[1].summary.case_number == "X0002"
    assert record.cases[1].summary.edit_status == EditStatus.UNCHANGED
def test_deleted_charge_does_not_block():
    """"""
    record, questions = RecordCreator.build_record(
        search("two_cases_two_charges_each"),
        "username",
        "password",
        (),
        {
            "X0001": {
                "summary": {
                    "edit_status": "UPDATE"
                },
                "charges": {
                    "X0001-1": {
                        "edit_status": "UPDATE",
                        "date": "1/1/2020",
                        "disposition": {
                            "date": "2/1/2020",
                            "ruling": "Convicted"
                        },
                        "level": "Misdemeanor Class A",
                    }
                },
            },
        },
        date.today(),
        LRUCache(4),
    )
    assert record.cases[0].summary.case_number == "X0001"
    assert record.cases[0].summary.edit_status == EditStatus.UPDATE
    assert record.cases[0].charges[0].edit_status == EditStatus.UPDATE
    assert record.cases[0].charges[1].edit_status == EditStatus.UNCHANGED

    assert record.cases[1].summary.case_number == "X0002"
    assert (record.cases[1].charges[0].expungement_result.charge_eligibility.
            status == ChargeEligibilityStatus.WILL_BE_ELIGIBLE)

    record, questions = RecordCreator.build_record(
        search("two_cases_two_charges_each"),
        "username",
        "password",
        (),
        {
            "X0001": {
                "summary": {
                    "edit_status": "UPDATE",
                },
                "charges": {
                    "X0001-1": {
                        "edit_status": "DELETE"
                    },
                    "X0001-2": {
                        "edit_status": "DELETE"
                    },
                },
            }
        },
        date.today(),
        LRUCache(4),
    )

    assert record.cases[0].summary.case_number == "X0001"
    assert record.cases[0].summary.edit_status == EditStatus.UPDATE
    assert record.cases[0].charges[0].edit_status == EditStatus.DELETE
    assert record.cases[0].charges[1].edit_status == EditStatus.DELETE

    assert record.cases[1].summary.case_number == "X0002"
    assert (record.cases[1].charges[0].expungement_result.charge_eligibility.
            status == ChargeEligibilityStatus.ELIGIBLE_NOW)
Exemple #13
0
class Crawler:
    cached_links = LRUCache(1000)

    @staticmethod
    def attempt_login(session: Session, username, password) -> str:
        url = URL.login_url()
        payload = Payload.login_payload(username, password)
        response = session.post(url, data=payload)
        if Crawler._succeed_login(response):
            return response.text
        elif "Oregon eCourt is temporarily unavailable due to maintenance" in response.text:
            raise OECIUnavailable
        else:
            raise InvalidOECIUsernamePassword

    @staticmethod
    def fetch_link(link: str, session: Session = None):
        if session:
            response = session.get(link)
            Crawler.cached_links[link] = response
            return response
        else:
            return Crawler.cached_links[link]

    @staticmethod
    def search(session: Session,
               login_response,
               first_name,
               last_name,
               middle_name="",
               birth_date="") -> List[OeciCase]:
        search_url = URL.search_url()
        node_response = Crawler._fetch_search_page(session, search_url,
                                                   login_response)
        oeci_search_result = Crawler._search_record(session, node_response,
                                                    search_url, first_name,
                                                    last_name, middle_name,
                                                    birth_date)
        case_limit = 300
        if len(oeci_search_result.cases) >= case_limit:
            raise ValueError(
                f"Found {len(oeci_search_result.cases)} matching cases, exceeding the limit of {case_limit}. Please add a date of birth to your search."
            )
        else:
            # Parse search results (case detail pages)
            with ThreadPoolExecutor(max_workers=50) as executor:
                oeci_cases: List[OeciCase] = []
                for oeci_case in executor.map(
                        partial(Crawler._read_case, session),
                        oeci_search_result.cases):
                    oeci_cases.append(oeci_case)
            return oeci_cases

    @staticmethod
    def _search_record(session: Session, node_response, search_url, first_name,
                       last_name, middle_name, birth_date):
        payload = Crawler.__extract_payload(node_response, last_name,
                                            first_name, middle_name,
                                            birth_date)
        response = session.post(search_url, data=payload, timeout=30)
        record_parser = RecordParser()
        record_parser.feed(response.text)
        return record_parser

    @staticmethod
    def _read_case(session: Session, case_summary: CaseSummary) -> OeciCase:
        case_parser_data = Crawler._parse_case(session, case_summary)
        balance_due_in_cents = CaseCreator.compute_balance_due_in_cents(
            case_parser_data.balance_due)
        charges: List[OeciCharge] = []
        for charge_id, charge_dict in case_parser_data.hashed_charge_data.items(
        ):
            ambiguous_charge_id = f"{case_summary.case_number}-{charge_id}"
            charge = Crawler._build_oeci_charge(charge_id, ambiguous_charge_id,
                                                charge_dict, case_parser_data,
                                                balance_due_in_cents)
            charges.append(charge)
        updated_case_summary = replace(
            case_summary,
            balance_due_in_cents=balance_due_in_cents,
            edit_status=EditStatus.UNCHANGED)
        return OeciCase(updated_case_summary, charges=tuple(charges))

    @staticmethod
    def _fetch_search_page(session, url, login_response):
        node_parser = NodeParser()
        node_parser.feed(login_response)
        payload = {"NodeID": node_parser.node_id, "NodeDesc": "All+Locations"}
        return session.post(url, data=payload)

    @staticmethod
    def _parse_case(session: Session, case: CaseSummary):
        response = Crawler.fetch_link(case.case_detail_link, session)
        if response.status_code == 200 and response.text:
            return CaseParser.feed(response.text)
        else:
            raise ValueError(
                f"Failed to fetch case detail page. Please rerun the search.")