From 86ee26ddc0d6f2f12be93863ed00c5b20e956f2c Mon Sep 17 00:00:00 2001 From: Michael Chouinard Date: Tue, 5 Mar 2024 13:34:44 -0500 Subject: [PATCH] Add DB migrations, fix factories, adjust tests temporarily --- .../versions/2024_03_05_drop_summary_table.py | 91 +++++ .../2024_03_05_updates_for_summary_tables.py | 352 ++++++++++++++++++ api/src/db/models/opportunity_models.py | 24 +- api/tests/lib/seed_local_db.py | 9 +- .../test_opportunity_route.py | 18 +- api/tests/src/db/models/factories.py | 139 ++++--- api/tests/src/db/models/test_factories.py | 5 - 7 files changed, 574 insertions(+), 64 deletions(-) create mode 100644 api/src/db/migrations/versions/2024_03_05_drop_summary_table.py create mode 100644 api/src/db/migrations/versions/2024_03_05_updates_for_summary_tables.py diff --git a/api/src/db/migrations/versions/2024_03_05_drop_summary_table.py b/api/src/db/migrations/versions/2024_03_05_drop_summary_table.py new file mode 100644 index 000000000..a92eab2ac --- /dev/null +++ b/api/src/db/migrations/versions/2024_03_05_drop_summary_table.py @@ -0,0 +1,91 @@ +"""drop summary table + +Revision ID: 81cb9c6033b3 +Revises: 5d58c38f2cac +Create Date: 2024-03-05 12:15:07.389544 + +""" +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "81cb9c6033b3" +down_revision = "5d58c38f2cac" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("opportunity_summary") + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "opportunity_summary", + sa.Column("opportunity_id", sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column("opportunity_status_id", sa.INTEGER(), autoincrement=False, nullable=True), + sa.Column("summary_description", sa.TEXT(), autoincrement=False, nullable=True), + sa.Column("is_cost_sharing", sa.BOOLEAN(), autoincrement=False, nullable=True), + sa.Column("close_date", sa.DATE(), autoincrement=False, nullable=True), + sa.Column("close_date_description", sa.TEXT(), autoincrement=False, nullable=True), + sa.Column("post_date", sa.DATE(), autoincrement=False, nullable=True), + sa.Column("archive_date", sa.DATE(), autoincrement=False, nullable=True), + sa.Column("unarchive_date", sa.DATE(), autoincrement=False, nullable=True), + sa.Column("expected_number_of_awards", sa.INTEGER(), autoincrement=False, nullable=True), + sa.Column( + "estimated_total_program_funding", sa.INTEGER(), autoincrement=False, nullable=True + ), + sa.Column("award_floor", sa.INTEGER(), autoincrement=False, nullable=True), + sa.Column("award_ceiling", sa.INTEGER(), autoincrement=False, nullable=True), + sa.Column("additional_info_url", sa.TEXT(), autoincrement=False, nullable=True), + sa.Column("additional_info_url_description", sa.TEXT(), autoincrement=False, nullable=True), + sa.Column("version_number", sa.INTEGER(), autoincrement=False, nullable=True), + sa.Column("modification_comments", sa.TEXT(), autoincrement=False, nullable=True), + sa.Column("funding_category_description", sa.TEXT(), autoincrement=False, nullable=True), + sa.Column( + "applicant_eligibility_description", sa.TEXT(), autoincrement=False, nullable=True + ), + sa.Column("agency_code", sa.TEXT(), autoincrement=False, nullable=True), + sa.Column("agency_name", sa.TEXT(), autoincrement=False, nullable=True), + sa.Column("agency_phone_number", sa.TEXT(), autoincrement=False, nullable=True), + sa.Column("agency_contact_description", sa.TEXT(), autoincrement=False, nullable=True), + sa.Column("agency_email_address", sa.TEXT(), autoincrement=False, nullable=True), + sa.Column( + "agency_email_address_description", sa.TEXT(), autoincrement=False, nullable=True + ), + sa.Column("can_send_mail", sa.BOOLEAN(), autoincrement=False, nullable=True), + sa.Column("publisher_profile_id", sa.INTEGER(), autoincrement=False, nullable=True), + sa.Column("publisher_user_id", sa.TEXT(), autoincrement=False, nullable=True), + sa.Column("updated_by", sa.TEXT(), autoincrement=False, nullable=True), + sa.Column("created_by", sa.TEXT(), autoincrement=False, nullable=True), + sa.Column( + "created_at", + postgresql.TIMESTAMP(timezone=True), + server_default=sa.text("now()"), + autoincrement=False, + nullable=False, + ), + sa.Column( + "updated_at", + postgresql.TIMESTAMP(timezone=True), + server_default=sa.text("now()"), + autoincrement=False, + nullable=False, + ), + sa.ForeignKeyConstraint( + ["opportunity_id"], + ["opportunity.opportunity_id"], + name="opportunity_summary_opportunity_id_opportunity_fkey", + ), + sa.ForeignKeyConstraint( + ["opportunity_status_id"], + ["lk_opportunity_status.opportunity_status_id"], + name="opportunity_summary_opportunity_status_id_lk_opportunit_ea00", + ), + sa.PrimaryKeyConstraint("opportunity_id", name="opportunity_summary_pkey"), + ) + # ### end Alembic commands ### diff --git a/api/src/db/migrations/versions/2024_03_05_updates_for_summary_tables.py b/api/src/db/migrations/versions/2024_03_05_updates_for_summary_tables.py new file mode 100644 index 000000000..11bc76a24 --- /dev/null +++ b/api/src/db/migrations/versions/2024_03_05_updates_for_summary_tables.py @@ -0,0 +1,352 @@ +"""Updates for summary tables + +Revision ID: 89cb2b016f84 +Revises: 81cb9c6033b3 +Create Date: 2024-03-05 12:15:57.820687 + +""" +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "89cb2b016f84" +down_revision = "81cb9c6033b3" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "opportunity_summary", + sa.Column("opportunity_summary_id", sa.Integer(), nullable=False), + sa.Column("opportunity_id", sa.Integer(), nullable=False), + sa.Column("summary_description", sa.Text(), nullable=True), + sa.Column("is_cost_sharing", sa.Boolean(), nullable=True), + sa.Column("is_forecast", sa.Boolean(), nullable=True), + sa.Column("post_date", sa.Date(), nullable=True), + sa.Column("close_date", sa.Date(), nullable=True), + sa.Column("close_date_description", sa.Text(), nullable=True), + sa.Column("archive_date", sa.Date(), nullable=True), + sa.Column("unarchive_date", sa.Date(), nullable=True), + sa.Column("expected_number_of_awards", sa.Integer(), nullable=True), + sa.Column("estimated_total_program_funding", sa.Integer(), nullable=True), + sa.Column("award_floor", sa.Integer(), nullable=True), + sa.Column("award_ceiling", sa.Integer(), nullable=True), + sa.Column("additional_info_url", sa.Text(), nullable=True), + sa.Column("additional_info_url_description", sa.Text(), nullable=True), + sa.Column("forecasted_post_date", sa.Date(), nullable=True), + sa.Column("forecasted_close_date", sa.Date(), nullable=True), + sa.Column("forecasted_close_date_description", sa.Text(), nullable=True), + sa.Column("forecasted_award_date", sa.Date(), nullable=True), + sa.Column("forecasted_project_start_date", sa.Date(), nullable=True), + sa.Column("fiscal_year", sa.Integer(), nullable=True), + sa.Column("revision_number", sa.Integer(), nullable=True), + sa.Column("modification_comments", sa.Text(), nullable=True), + sa.Column("funding_category_description", sa.Text(), nullable=True), + sa.Column("applicant_eligibility_description", sa.Text(), nullable=True), + sa.Column("agency_code", sa.Text(), nullable=True), + sa.Column("agency_name", sa.Text(), nullable=True), + sa.Column("agency_phone_number", sa.Text(), nullable=True), + sa.Column("agency_contact_description", sa.Text(), nullable=True), + sa.Column("agency_email_address", sa.Text(), nullable=True), + sa.Column("agency_email_address_description", sa.Text(), nullable=True), + sa.Column("is_deleted", sa.Boolean(), nullable=True), + sa.Column("can_send_mail", sa.Boolean(), nullable=True), + sa.Column("publisher_profile_id", sa.Integer(), nullable=True), + sa.Column("publisher_user_id", sa.Text(), nullable=True), + sa.Column("updated_by", sa.Text(), nullable=True), + sa.Column("created_by", sa.Text(), nullable=True), + sa.Column( + "created_at", + sa.TIMESTAMP(timezone=True), + server_default=sa.text("now()"), + nullable=False, + ), + sa.Column( + "updated_at", + sa.TIMESTAMP(timezone=True), + server_default=sa.text("now()"), + nullable=False, + ), + sa.ForeignKeyConstraint( + ["opportunity_id"], + ["opportunity.opportunity_id"], + name=op.f("opportunity_summary_opportunity_id_opportunity_fkey"), + ), + sa.PrimaryKeyConstraint("opportunity_summary_id", name=op.f("opportunity_summary_pkey")), + ) + op.create_table( + "current_opportunity_summary", + sa.Column("opportunity_id", sa.Integer(), nullable=False), + sa.Column("opportunity_summary_id", sa.Integer(), nullable=False), + sa.Column("opportunity_status_id", sa.Integer(), nullable=False), + sa.Column( + "created_at", + sa.TIMESTAMP(timezone=True), + server_default=sa.text("now()"), + nullable=False, + ), + sa.Column( + "updated_at", + sa.TIMESTAMP(timezone=True), + server_default=sa.text("now()"), + nullable=False, + ), + sa.ForeignKeyConstraint( + ["opportunity_id"], + ["opportunity.opportunity_id"], + name=op.f("current_opportunity_summary_opportunity_id_opportunity_fkey"), + ), + sa.ForeignKeyConstraint( + ["opportunity_status_id"], + ["lk_opportunity_status.opportunity_status_id"], + name=op.f( + "current_opportunity_summary_opportunity_status_id_lk_opportunity_status_fkey" + ), + ), + sa.ForeignKeyConstraint( + ["opportunity_summary_id"], + ["opportunity_summary.opportunity_summary_id"], + name=op.f( + "current_opportunity_summary_opportunity_summary_id_opportunity_summary_fkey" + ), + ), + sa.PrimaryKeyConstraint( + "opportunity_id", + "opportunity_summary_id", + name=op.f("current_opportunity_summary_pkey"), + ), + ) + op.create_table( + "link_applicant_type_summary", + sa.Column("opportunity_summary_id", sa.Integer(), nullable=False), + sa.Column("applicant_type_id", sa.Integer(), nullable=False), + sa.Column("legacy_applicant_type_id", sa.Integer(), nullable=True), + sa.Column("updated_by", sa.Text(), nullable=True), + sa.Column("created_by", sa.Text(), nullable=True), + sa.Column( + "created_at", + sa.TIMESTAMP(timezone=True), + server_default=sa.text("now()"), + nullable=False, + ), + sa.Column( + "updated_at", + sa.TIMESTAMP(timezone=True), + server_default=sa.text("now()"), + nullable=False, + ), + sa.ForeignKeyConstraint( + ["applicant_type_id"], + ["lk_applicant_type.applicant_type_id"], + name=op.f("link_applicant_type_summary_applicant_type_id_lk_applicant_type_fkey"), + ), + sa.ForeignKeyConstraint( + ["opportunity_summary_id"], + ["opportunity_summary.opportunity_summary_id"], + name=op.f( + "link_applicant_type_summary_opportunity_summary_id_opportunity_summary_fkey" + ), + ), + sa.PrimaryKeyConstraint( + "opportunity_summary_id", + "applicant_type_id", + name=op.f("link_applicant_type_summary_pkey"), + ), + ) + op.create_table( + "link_funding_category_summary", + sa.Column("opportunity_summary_id", sa.Integer(), nullable=False), + sa.Column("funding_category_id", sa.Integer(), nullable=False), + sa.Column("legacy_funding_category_id", sa.Integer(), nullable=True), + sa.Column("updated_by", sa.Text(), nullable=True), + sa.Column("created_by", sa.Text(), nullable=True), + sa.Column( + "created_at", + sa.TIMESTAMP(timezone=True), + server_default=sa.text("now()"), + nullable=False, + ), + sa.Column( + "updated_at", + sa.TIMESTAMP(timezone=True), + server_default=sa.text("now()"), + nullable=False, + ), + sa.ForeignKeyConstraint( + ["funding_category_id"], + ["lk_funding_category.funding_category_id"], + name=op.f("link_funding_category_summary_funding_category_id_lk_funding_category_fkey"), + ), + sa.ForeignKeyConstraint( + ["opportunity_summary_id"], + ["opportunity_summary.opportunity_summary_id"], + name=op.f( + "link_funding_category_summary_opportunity_summary_id_opportunity_summary_fkey" + ), + ), + sa.PrimaryKeyConstraint( + "opportunity_summary_id", + "funding_category_id", + name=op.f("link_funding_category_summary_pkey"), + ), + ) + op.create_table( + "link_funding_instrument_summary", + sa.Column("opportunity_summary_id", sa.Integer(), nullable=False), + sa.Column("funding_instrument_id", sa.Integer(), nullable=False), + sa.Column("legacy_funding_instrument_id", sa.Integer(), nullable=True), + sa.Column("updated_by", sa.Text(), nullable=True), + sa.Column("created_by", sa.Text(), nullable=True), + sa.Column( + "created_at", + sa.TIMESTAMP(timezone=True), + server_default=sa.text("now()"), + nullable=False, + ), + sa.Column( + "updated_at", + sa.TIMESTAMP(timezone=True), + server_default=sa.text("now()"), + nullable=False, + ), + sa.ForeignKeyConstraint( + ["funding_instrument_id"], + ["lk_funding_instrument.funding_instrument_id"], + name=op.f( + "link_funding_instrument_summary_funding_instrument_id_lk_funding_instrument_fkey" + ), + ), + sa.ForeignKeyConstraint( + ["opportunity_summary_id"], + ["opportunity_summary.opportunity_summary_id"], + name=op.f( + "link_funding_instrument_summary_opportunity_summary_id_opportunity_summary_fkey" + ), + ), + sa.PrimaryKeyConstraint( + "opportunity_summary_id", + "funding_instrument_id", + name=op.f("link_funding_instrument_summary_pkey"), + ), + ) + op.drop_table("link_funding_category_opportunity") + op.drop_table("link_funding_instrument_opportunity") + op.drop_table("link_applicant_type_opportunity") + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "link_applicant_type_opportunity", + sa.Column("opportunity_id", sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column("applicant_type_id", sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column("updated_by", sa.TEXT(), autoincrement=False, nullable=True), + sa.Column("created_by", sa.TEXT(), autoincrement=False, nullable=True), + sa.Column( + "created_at", + postgresql.TIMESTAMP(timezone=True), + server_default=sa.text("now()"), + autoincrement=False, + nullable=False, + ), + sa.Column( + "updated_at", + postgresql.TIMESTAMP(timezone=True), + server_default=sa.text("now()"), + autoincrement=False, + nullable=False, + ), + sa.ForeignKeyConstraint( + ["applicant_type_id"], + ["lk_applicant_type.applicant_type_id"], + name="link_applicant_type_opportunity_applicant_type_id_lk_ap_7903", + ), + sa.ForeignKeyConstraint( + ["opportunity_id"], + ["opportunity.opportunity_id"], + name="link_applicant_type_opportunity_opportunity_id_opportunity_fkey", + ), + sa.PrimaryKeyConstraint( + "opportunity_id", "applicant_type_id", name="link_applicant_type_opportunity_pkey" + ), + ) + op.create_table( + "link_funding_instrument_opportunity", + sa.Column("opportunity_id", sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column("funding_instrument_id", sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column("updated_by", sa.TEXT(), autoincrement=False, nullable=True), + sa.Column("created_by", sa.TEXT(), autoincrement=False, nullable=True), + sa.Column( + "created_at", + postgresql.TIMESTAMP(timezone=True), + server_default=sa.text("now()"), + autoincrement=False, + nullable=False, + ), + sa.Column( + "updated_at", + postgresql.TIMESTAMP(timezone=True), + server_default=sa.text("now()"), + autoincrement=False, + nullable=False, + ), + sa.ForeignKeyConstraint( + ["funding_instrument_id"], + ["lk_funding_instrument.funding_instrument_id"], + name="link_funding_instrument_opportunity_funding_instrument__68d6", + ), + sa.ForeignKeyConstraint( + ["opportunity_id"], + ["opportunity.opportunity_id"], + name="link_funding_instrument_opportunity_opportunity_id_oppo_9420", + ), + sa.PrimaryKeyConstraint( + "opportunity_id", + "funding_instrument_id", + name="link_funding_instrument_opportunity_pkey", + ), + ) + op.create_table( + "link_funding_category_opportunity", + sa.Column("opportunity_id", sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column("funding_category_id", sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column("updated_by", sa.TEXT(), autoincrement=False, nullable=True), + sa.Column("created_by", sa.TEXT(), autoincrement=False, nullable=True), + sa.Column( + "created_at", + postgresql.TIMESTAMP(timezone=True), + server_default=sa.text("now()"), + autoincrement=False, + nullable=False, + ), + sa.Column( + "updated_at", + postgresql.TIMESTAMP(timezone=True), + server_default=sa.text("now()"), + autoincrement=False, + nullable=False, + ), + sa.ForeignKeyConstraint( + ["funding_category_id"], + ["lk_funding_category.funding_category_id"], + name="link_funding_category_opportunity_funding_category_id_l_4add", + ), + sa.ForeignKeyConstraint( + ["opportunity_id"], + ["opportunity.opportunity_id"], + name="link_funding_category_opportunity_opportunity_id_opport_eb65", + ), + sa.PrimaryKeyConstraint( + "opportunity_id", "funding_category_id", name="link_funding_category_opportunity_pkey" + ), + ) + op.drop_table("link_funding_instrument_summary") + op.drop_table("link_funding_category_summary") + op.drop_table("link_applicant_type_summary") + op.drop_table("current_opportunity_summary") + op.drop_table("opportunity_summary") + # ### end Alembic commands ### diff --git a/api/src/db/models/opportunity_models.py b/api/src/db/models/opportunity_models.py index 681ce61f5..e4f518e98 100644 --- a/api/src/db/models/opportunity_models.py +++ b/api/src/db/models/opportunity_models.py @@ -59,19 +59,24 @@ class Opportunity(Base, TimestampMixin): @property def summary(self) -> "OpportunitySummary | None": + """ + Utility getter method for converting an Opportunity in our endpoints + + This handles mapping the current opportunity summary to the "summary" object + in our API responses - handling nullablity as well. + """ if self.current_opportunity_summary is None: return None return self.current_opportunity_summary.opportunity_summary + class OpportunitySummary(Base, TimestampMixin): __tablename__ = "opportunity_summary" opportunity_summary_id: Mapped[int] = mapped_column(primary_key=True) - opportunity_id: Mapped[int] = mapped_column( - ForeignKey(Opportunity.opportunity_id) - ) + opportunity_id: Mapped[int] = mapped_column(ForeignKey(Opportunity.opportunity_id)) opportunity: Mapped[Opportunity] = relationship(Opportunity) summary_description: Mapped[str | None] @@ -147,6 +152,7 @@ def applicant_types(self) -> list[ApplicantType]: # Helper method for serialization of the API response return [a.applicant_type for a in self.link_applicant_types] + class OpportunityAssistanceListing(Base, TimestampMixin): __tablename__ = "opportunity_assistance_listing" @@ -228,13 +234,19 @@ class LinkApplicantTypeSummary(Base, TimestampMixin): class CurrentOpportunitySummary(Base, TimestampMixin): __tablename__ = "current_opportunity_summary" - opportunity_id: Mapped[int] = mapped_column(ForeignKey(Opportunity.opportunity_id), primary_key=True) - opportunity: Mapped[Opportunity] = relationship() + opportunity_id: Mapped[int] = mapped_column( + ForeignKey(Opportunity.opportunity_id), primary_key=True + ) + opportunity: Mapped[Opportunity] = relationship( + single_parent=True, cascade="all, delete-orphan" + ) opportunity_summary_id: Mapped[int] = mapped_column( ForeignKey(OpportunitySummary.opportunity_summary_id), primary_key=True ) - opportunity_summary: Mapped[OpportunitySummary] = relationship() + opportunity_summary: Mapped[OpportunitySummary] = relationship( + single_parent=True, cascade="all, delete-orphan" + ) opportunity_status: Mapped[OpportunityStatus] = mapped_column( "opportunity_status_id", diff --git a/api/tests/lib/seed_local_db.py b/api/tests/lib/seed_local_db.py index 74322631e..d491bd5cc 100644 --- a/api/tests/lib/seed_local_db.py +++ b/api/tests/lib/seed_local_db.py @@ -24,7 +24,14 @@ def _build_opportunities(db_session: db.Session) -> None: max_opportunity_id = 0 factories.OpportunityFactory.reset_sequence(value=max_opportunity_id + 1) - factories.OpportunityFactory.create_batch(size=25) + + # Create a few opportunities in various scenarios + factories.OpportunityFactory.create_batch(size=5, is_forecasted_summary=True) + factories.OpportunityFactory.create_batch(size=5, is_posted_summary=True) + factories.OpportunityFactory.create_batch(size=5, is_closed_summary=True) + factories.OpportunityFactory.create_batch(size=5, is_archived_non_forecast_summary=True) + factories.OpportunityFactory.create_batch(size=5, is_archived_forecast_summary=True) + factories.OpportunityFactory.create_batch(size=5, no_current_summary=True) # Also seed the topportunity table for now in the same way max_opportunity_id = db_session.query(func.max(TransferTopportunity.opportunity_id)).scalar() diff --git a/api/tests/src/api/opportunities_v0_1/test_opportunity_route.py b/api/tests/src/api/opportunities_v0_1/test_opportunity_route.py index 6d0b87bcd..4ff1ba9c2 100644 --- a/api/tests/src/api/opportunities_v0_1/test_opportunity_route.py +++ b/api/tests/src/api/opportunities_v0_1/test_opportunity_route.py @@ -93,9 +93,11 @@ def validate_opportunity(db_opportunity: Opportunity, resp_opportunity: dict): resp_opportunity["opportunity_assistance_listings"], ) - assert set(db_opportunity.funding_instruments) == set(resp_opportunity["funding_instruments"]) - assert set(db_opportunity.funding_categories) == set(resp_opportunity["funding_categories"]) - assert set(db_opportunity.applicant_types) == set(resp_opportunity["applicant_types"]) + # TODO - these have been moved in the DB model - will be fixed in https://github.com/HHS/simpler-grants-gov/issues/1364 + # assert db_opportunity.opportunity_status == resp_opportunity["opportunity_status"] + # assert set(db_opportunity.funding_instruments) == set(resp_opportunity["funding_instruments"]) + # assert set(db_opportunity.funding_categories) == set(resp_opportunity["funding_categories"]) + # assert set(db_opportunity.applicant_types) == set(resp_opportunity["applicant_types"]) def validate_opportunity_summary(db_summary: OpportunitySummary, resp_summary: dict): @@ -103,7 +105,6 @@ def validate_opportunity_summary(db_summary: OpportunitySummary, resp_summary: d assert resp_summary is None return - assert db_summary.opportunity_status == resp_summary["opportunity_status"] assert db_summary.summary_description == resp_summary["summary_description"] assert db_summary.is_cost_sharing == resp_summary["is_cost_sharing"] assert str(db_summary.close_date) == resp_summary["close_date"] @@ -287,11 +288,12 @@ def test_opportunity_search_invalid_request_422( {}, # Set all the non-opportunity model objects to null/empty { - "summary": None, + "current_opportunity_summary": None, "opportunity_assistance_listings": [], - "link_funding_instruments": [], - "link_funding_categories": [], - "link_applicant_types": [], + # TODO: https://github.com/HHS/simpler-grants-gov/issues/1364 + # "link_funding_instruments": [], + # "link_funding_categories": [], + # "link_applicant_types": [], }, ], ) diff --git a/api/tests/src/db/models/factories.py b/api/tests/src/db/models/factories.py index 1649327d6..c223c3f5f 100644 --- a/api/tests/src/db/models/factories.py +++ b/api/tests/src/db/models/factories.py @@ -96,7 +96,6 @@ class Meta: revision_number = 0 # We'll want to consider how we handle this when we add history - opportunity_assistance_listings = factory.RelatedFactoryList( "tests.src.db.models.factories.OpportunityAssistanceListingFactory", factory_related_name="opportunity", @@ -106,8 +105,10 @@ class Meta: # By default we'll add a current opportunity summary which will be POSTED # if you'd like to easily modify the values, see the possible traits below in the # Params class section - current_opportunity_summary = factory.RelatedFactory("tests.src.db.models.factories.CurrentOpportunitySummaryFactory", factory_related_name="opportunity") - + current_opportunity_summary = factory.RelatedFactory( + "tests.src.db.models.factories.CurrentOpportunitySummaryFactory", + factory_related_name="opportunity", + ) class Params: # These are common scenarios we might want for an opportunity. @@ -118,11 +119,18 @@ class Params: no_current_summary = factory.Trait(current_opportunity_summary=None) # We set a trait for the OpportunitySummaryFactory for each of these as well as set the opportunity status - is_posted = factory.Trait(current_opportunity_summary__opportunity_status=OpportunityStatus.POSTED, current_opportunity_summary__opportunity_summary__is_posted_summary=True) - is_forecasted = factory.Trait(current_opportunity_summary__opportunity_status=OpportunityStatus.FORECASTED, current_opportunity_summary__opportunity_summary__is_forecasted_summary=True) - is_closed = factory.Trait(current_opportunity_summary__opportunity_status=OpportunityStatus.CLOSED, current_opportunity_summary__opportunity_summary__is_closed_summary=True) - is_archived_non_forecast = factory.Trait(current_opportunity_summary__opportunity_status=OpportunityStatus.ARCHIVED, current_opportunity_summary__opportunity_summary__is_archived_non_forecast_summary=True) - is_archived_forecast = factory.Trait(current_opportunity_summary__opportunity_status=OpportunityStatus.ARCHIVED, current_opportunity_summary__opportunity_summary__is_archived_forecast_summary=True) + is_posted_summary = factory.Trait(current_opportunity_summary__is_posted_summary=True) + is_forecasted_summary = factory.Trait( + current_opportunity_summary__is_forecasted_summary=True + ) + is_closed_summary = factory.Trait(current_opportunity_summary__is_closed_summary=True) + is_archived_non_forecast_summary = factory.Trait( + current_opportunity_summary__is_archived_non_forecast_summary=True + ) + is_archived_forecast_summary = factory.Trait( + current_opportunity_summary__is_archived_forecast_summary=True + ) + class OpportunitySummaryFactory(BaseFactory): class Meta: @@ -131,7 +139,6 @@ class Meta: opportunity = factory.SubFactory(OpportunityFactory) opportunity_id = factory.LazyAttribute(lambda s: s.opportunity.opportunity_id) - summary_description = factory.LazyFunction(lambda: f"Example summary - {fake.paragraph()}") is_cost_sharing = factory.Faker("boolean") @@ -140,17 +147,17 @@ class Meta: # Forecasted records don't have a close date close_date = factory.Maybe( - decider=factory.LazyAttribute( - lambda s: s.is_forecast - ), + decider=factory.LazyAttribute(lambda s: s.is_forecast), # If forecasted, don't set a close date yes_declaration=None, # otherwise a future date no_declaration=factory.Faker("date_between", start_date="+2w", end_date="+3w"), ) - close_date_description = factory.Maybe(decider=factory.LazyAttribute(lambda s: s.close_date is None), - yes_declaration=None, - no_declaration=factory.Faker("paragraph", nb_sentences=1)) + close_date_description = factory.Maybe( + decider=factory.LazyAttribute(lambda s: s.close_date is None), + yes_declaration=None, + no_declaration=factory.Faker("paragraph", nb_sentences=1), + ) # Just a random recent post date post_date = factory.Faker("date_between", start_date="-3w", end_date="now") @@ -198,45 +205,41 @@ class Meta: # Forecasted values are only set if is_forecast=True forecasted_post_date = factory.Maybe( - decider=factory.LazyAttribute( - lambda s: s.is_forecast - ), + decider=factory.LazyAttribute(lambda s: s.is_forecast), # If forecasted, set it in the future yes_declaration=factory.Faker("date_between", start_date="+2w", end_date="+3w"), # otherwise don't set no_declaration=None, ) forecasted_close_date = factory.Maybe( - decider=factory.LazyAttribute( - lambda s: s.is_forecast - ), + decider=factory.LazyAttribute(lambda s: s.is_forecast), # If forecasted, set it in the future yes_declaration=factory.Faker("date_between", start_date="+6w", end_date="+12w"), # otherwise don't set no_declaration=None, ) - forecasted_close_date_description = factory.Maybe(decider=factory.LazyAttribute(lambda s: s.forecasted_close_date is None), - yes_declaration=None, - no_declaration=factory.Faker("paragraph", nb_sentences=1)) + forecasted_close_date_description = factory.Maybe( + decider=factory.LazyAttribute(lambda s: s.forecasted_close_date is None), + yes_declaration=None, + no_declaration=factory.Faker("paragraph", nb_sentences=1), + ) forecasted_award_date = factory.Maybe( - decider=factory.LazyAttribute( - lambda s: s.is_forecast - ), + decider=factory.LazyAttribute(lambda s: s.is_forecast), # If forecasted, set it in the future yes_declaration=factory.Faker("date_between", start_date="+26w", end_date="+30w"), # otherwise don't set no_declaration=None, ) forecasted_project_start_date = factory.Maybe( - decider=factory.LazyAttribute( - lambda s: s.is_forecast - ), + decider=factory.LazyAttribute(lambda s: s.is_forecast), # If forecasted, set it in the future yes_declaration=factory.Faker("date_between", start_date="+30w", end_date="+52w"), # otherwise don't set no_declaration=None, ) - fiscal_year = factory.LazyAttribute(lambda s: s.forecasted_project_start_date.year if s.forecasted_project_start_date else None) + fiscal_year = factory.LazyAttribute( + lambda s: s.forecasted_project_start_date.year if s.forecasted_project_start_date else None + ) is_deleted = False @@ -269,28 +272,71 @@ class Params: is_posted_summary = factory.Trait(is_forecast=False) is_forecasted_summary = factory.Trait(is_forecast=True) - # Set the close date to the past - is_closed_summary = factory.Trait(is_forecast=False, close_date=factory.Faker("date_between", start_date="-3w", end_date="-1w")) + # For these scenarios, adjust a few dates to make more sense + is_closed_summary = factory.Trait( + is_forecast=False, + post_date=factory.Faker("date_between", start_date="-6w", end_date="-5w"), + close_date=factory.Faker("date_between", start_date="-3w", end_date="-1w"), + ) + + is_archived_non_forecast_summary = factory.Trait( + is_forecast=False, + post_date=factory.Faker("date_between", start_date="-6w", end_date="-5w"), + close_date=factory.Faker("date_between", start_date="-3w", end_date="-2w"), + archive_date=factory.Faker("date_between", start_date="-2w", end_date="-1w"), + ) + is_archived_forecast_summary = factory.Trait( + is_forecast=True, + post_date=factory.Faker("date_between", start_date="-6w", end_date="-5w"), + archive_date=factory.Faker("date_between", start_date="-2w", end_date="-1w"), + ) - is_archived_non_forecast_summary = factory.Trait(is_forecast=False, close_date=factory.Faker("date_between", start_date="-3w", end_date="-2w"), archive_date=factory.Faker("date_between", start_date="-2w", end_date="-1w")) - is_archived_forecast_summary = factory.Trait(is_forecast=True, archive_date=factory.Faker("date_between", start_date="-2w", end_date="-1w")) + is_non_public_non_forecast_summary = factory.Trait( + is_forecast=False, + post_date=factory.Faker("date_between", start_date="+3w", end_date="+4w"), + ) + is_non_public_forecast_summary = factory.Trait( + is_forecast=True, + post_date=factory.Faker("date_between", start_date="+3w", end_date="+4w"), + ) - is_non_public_non_forecast_summary = factory.Trait(is_forecast=False, post_date=factory.Faker("date_between", start_date="+3w", end_date="+4w")) - is_non_public_forecast_summary = factory.Trait(is_forecast=True, post_date=factory.Faker("date_between", start_date="+3w", end_date="+4w")) class CurrentOpportunitySummaryFactory(BaseFactory): - class Meta: model = opportunity_models.CurrentOpportunitySummary opportunity = factory.SubFactory(OpportunityFactory) opportunity_id = factory.LazyAttribute(lambda a: a.opportunity.opportunity_id) - opportunity_summary = factory.SubFactory(OpportunitySummaryFactory, opportunity=factory.SelfAttribute("..opportunity")) - opportunity_summary_id = factory.LazyAttribute(lambda a: a.opportunity_summary.opportunity_summary_id) + opportunity_summary = factory.SubFactory( + OpportunitySummaryFactory, opportunity=factory.SelfAttribute("..opportunity") + ) + opportunity_summary_id = factory.LazyAttribute( + lambda a: a.opportunity_summary.opportunity_summary_id + ) opportunity_status = OpportunityStatus.POSTED + class Params: + is_posted_summary = factory.Trait( + opportunity_status=OpportunityStatus.POSTED, opportunity_summary__is_posted_summary=True + ) + is_forecasted_summary = factory.Trait( + opportunity_status=OpportunityStatus.FORECASTED, + opportunity_summary__is_forecasted_summary=True, + ) + is_closed_summary = factory.Trait( + opportunity_status=OpportunityStatus.CLOSED, opportunity_summary__is_closed_summary=True + ) + is_archived_non_forecast_summary = factory.Trait( + opportunity_status=OpportunityStatus.ARCHIVED, + opportunity_summary__is_archived_non_forecast_summary=True, + ) + is_archived_forecast_summary = factory.Trait( + opportunity_status=OpportunityStatus.ARCHIVED, + opportunity_summary__is_archived_forecast_summary=True, + ) + class OpportunityAssistanceListingFactory(BaseFactory): class Meta: @@ -310,7 +356,9 @@ class Meta: model = opportunity_models.LinkFundingInstrumentSummary opportunity_summary = factory.SubFactory(OpportunitySummaryFactory) - opportunity_summary_id = factory.LazyAttribute(lambda f: f.opportunity_summary.opportunity_summary_id) + opportunity_summary_id = factory.LazyAttribute( + lambda f: f.opportunity_summary.opportunity_summary_id + ) # We use an iterator here to keep the values unique when generated by the opportunity factory funding_instrument = factory.Iterator(FundingInstrument) @@ -321,7 +369,9 @@ class Meta: model = opportunity_models.LinkFundingCategorySummary opportunity_summary = factory.SubFactory(OpportunitySummaryFactory) - opportunity_summary_id = factory.LazyAttribute(lambda f: f.opportunity_summary.opportunity_summary_id) + opportunity_summary_id = factory.LazyAttribute( + lambda f: f.opportunity_summary.opportunity_summary_id + ) # We use an iterator here to keep the values unique when generated by the opportunity factory funding_category = factory.Iterator(FundingCategory) @@ -332,7 +382,9 @@ class Meta: model = opportunity_models.LinkApplicantTypeSummary opportunity_summary = factory.SubFactory(OpportunitySummaryFactory) - opportunity_summary_id = factory.LazyAttribute(lambda f: f.opportunity_summary.opportunity_summary_id) + opportunity_summary_id = factory.LazyAttribute( + lambda f: f.opportunity_summary.opportunity_summary_id + ) # We use an iterator here to keep the values unique when generated by the opportunity factory applicant_type = factory.Iterator(ApplicantType) @@ -407,4 +459,3 @@ class ForeignTopportunityFactory(factory.DictFactory): created_date = factory.Faker("date_between", start_date="-10y", end_date="-5y") last_upd_date = factory.Faker("date_between", start_date="-5y", end_date="today") - diff --git a/api/tests/src/db/models/test_factories.py b/api/tests/src/db/models/test_factories.py index d227ea61e..9af409d5a 100644 --- a/api/tests/src/db/models/test_factories.py +++ b/api/tests/src/db/models/test_factories.py @@ -82,8 +82,3 @@ def test_opportunity_factory_create(enable_factory_create, db_session: db.Sessio null_params = {"agency": None} opportunity = OpportunityFactory.create(**null_params) validate_opportunity_record(opportunity, null_params) - - -def test_thing(enable_factory_create, db_session: db.Session): - x = OpportunityFactory.create(no_current_summary=True) - print(x) \ No newline at end of file