Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix feature tests #1263

Merged
merged 8 commits into from
Feb 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,12 @@ updates:
include: "scope"
labels:
- "dependencies"
- package-ecosystem: "npm"
directory: "/tests/cypress" # Cypress testing
schedule:
interval: "daily"
commit-message:
prefix: "chore"
include: "scope"
labels:
- "dependencies"
37 changes: 4 additions & 33 deletions .github/workflows/tests-feature.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
name: Feature tests

on:
workflow_dispatch:
on: push

defaults:
run:
Expand All @@ -14,41 +13,13 @@ jobs:
- name: Check out code
uses: actions/checkout@v3

- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2

- name: Build Dockerfile
id: build-client-image
uses: docker/build-push-action@v4
with:
builder: ${{ steps.buildx.outputs.name }}
cache-from: type=gha,scope=cal-itp
cache-to: type=gha,scope=cal-itp,mode=max
context: .
file: appcontainer/Dockerfile
push: false
load: true
tags: benefits_client:${{ github.sha }}

- name: Start app
run: |
docker run \
--detach \
--env-file tests/cypress/.env.tests \
-p 8000:8000 \
-v ${{ github.workspace }}/fixtures:/home/calitp/app/fixtures \
benefits_client:${{ github.sha }}

- name: Start server
run: |
docker run \
--detach \
-p 5000:5000 \
ghcr.io/cal-itp/eligibility-server:dev
touch .env
docker compose up --detach client server
Copy link
Contributor Author

@afeld afeld Feb 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was able to simplify, and more closely match our local setup.


- name: Run feature tests
uses: cypress-io/github-action@v2
uses: cypress-io/github-action@v5
env:
CYPRESS_baseUrl: http://localhost:8000
with:
Expand Down
20 changes: 13 additions & 7 deletions benefits/core/migrations/0002_sample_data.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Data migration which loads sample data.
Set environment variable DJANGO_LOAD_SAMPLE_DATA to False to skip loading sample data.
"""
import json
import os

from django.conf import settings
Expand Down Expand Up @@ -201,14 +202,19 @@ def load_sample_data(app, *args, **kwargs):

TransitAgency = app.get_model("core", "TransitAgency")

# load the sample data from a JSON file so that it can be accessed by Cypress as well
Copy link
Contributor Author

@afeld afeld Feb 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See this note. Essentially going back to fixtures, just for the sake of the sample data. Doesn't change anything in Azure.

sample_agency_data = os.path.join(os.path.dirname(__file__), "sample_agency.json")
with open(sample_agency_data) as f:
sample_agency = json.load(f)

mst_agency = TransitAgency.objects.create(
slug="mst",
short_name=os.environ.get("MST_AGENCY_SHORT_NAME", "MST (sample)"),
long_name=os.environ.get("MST_AGENCY_LONG_NAME", "Monterey-Salinas Transit (sample)"),
agency_id="mst",
merchant_id="mst",
info_url="https://mst.org/benefits",
phone="888-678-2871",
slug=sample_agency["slug"],
short_name=os.environ.get("MST_AGENCY_SHORT_NAME", sample_agency["short_name"]),
long_name=os.environ.get("MST_AGENCY_LONG_NAME", sample_agency["long_name"]),
agency_id=sample_agency["agency_id"],
merchant_id=sample_agency["merchant_id"],
info_url=sample_agency["info_url"],
phone=sample_agency["phone"],
active=True,
private_key=client_private_key,
public_key=client_public_key,
Expand Down
9 changes: 9 additions & 0 deletions benefits/core/migrations/sample_agency.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"slug": "mst",
"short_name": "MST (sample)",
"long_name": "Monterey-Salinas Transit (sample)",
"agency_id": "mst",
"merchant_id": "mst",
"info_url": "https://mst.org/benefits",
"phone": "888-678-2871"
}
1 change: 1 addition & 0 deletions benefits/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ def _filter_empty(ls):
ANALYTICS_KEY = os.environ.get("ANALYTICS_KEY")

# rate limit configuration
# these should match the values in rate-limit.cy.js

# number of requests allowed in the given period
RATE_LIMIT = int(os.environ.get("DJANGO_RATE_LIMIT", 5))
Expand Down
2 changes: 2 additions & 0 deletions docs/tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ CYPRESS_baseUrl=http://localhost:8000 npm run cypress:open

See `tests/cypress/package.json` for more cypress scripts.

As of Cypress 12.5.1 with Firefox 109, there is a CSRF issue that prevents the tests from passing; unclear if this is a bug in Cypress or what. Use one of the other browser options.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tricksy.


## Pytest

The tests done at a request/unit level are run via [pytest-django](https://pytest-django.readthedocs.io/en/latest/index.html).
Expand Down
1 change: 1 addition & 0 deletions tests/cypress/fixtures/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The [user data](users.json) corresponds to [the sample data for the eligibility server](https://github.com/cal-itp/eligibility-server/blob/dev/data/server.json).
3 changes: 2 additions & 1 deletion tests/cypress/fixtures/transit-agencies.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const agencies = require("../../../fixtures/05_transitagency.json");
const agency = require("../../../benefits/core/migrations/sample_agency.json");
const agencies = [{ fields: agency }];

export default agencies;
10 changes: 3 additions & 7 deletions tests/cypress/fixtures/users.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
{
"eligible": {
"name": "Garcia",
"sub": "A1234567"
"name": "Gonzales",
"sub": "32587"
},
"ineligible": {
"name": "Bob",
"sub": "A1234567"
},
"invalidSub": {
"name": "Garcia",
"sub": "A12347"
"sub": "12345"
}
}
16 changes: 8 additions & 8 deletions tests/cypress/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion tests/cypress/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@
"license": "AGPL-3.0-or-later",
"private": true,
"devDependencies": {
"cypress": "^10.9.0"
"cypress": "^12.5.1"
}
}
39 changes: 15 additions & 24 deletions tests/cypress/specs/feature/eligibility.cy.js
Original file line number Diff line number Diff line change
@@ -1,49 +1,40 @@
const agencies = require("../../fixtures/transit-agencies");
const users = require("../../fixtures/users.json");

const eligibility_url = "/eligibility/confirm";

describe("Eligibility confirmation flow", () => {
thekaveman marked this conversation as resolved.
Show resolved Hide resolved
beforeEach(() => {
cy.visit("/");
cy.contains(agencies[0].fields.short_name).click();
cy.contains("Get started").click();

// agency selection
cy.contains("Choose Your Provider").click();
cy.contains(agencies[0].fields.long_name).click();

// select Courtesy Card
// TODO find a more robust way to do this
cy.get('#form-verifier-selection [type="radio"]').check("2");
cy.get("#form-verifier-selection button[type='submit']").click();
cy.contains("Continue").click();
});

it("Confirms an eligible user", () => {
cy.get("#sub").type(users.eligible.sub);
cy.get("#name").type(users.eligible.name);
cy.get("input[type='submit']").click();
cy.get("#form-eligibility-verification button[type='submit']").click();

cy.contains("Great! You’re eligible for a discount!");
cy.contains("Your eligibility is confirmed!");
});

it("Rejects an ineligible user", () => {
cy.get("#sub").type(users.ineligible.sub);
cy.get("#name").type(users.ineligible.name);
cy.get("input[type='submit']").click();
cy.get("#form-eligibility-verification button[type='submit']").click();

cy.contains("We can’t confirm your age");
cy.contains(
"You may still be eligible for a discount, but we can’t verify your age",
);
});

it("Validates an invalid number/id field", () => {
cy.get("#sub").type(users.invalidSub.sub);
cy.get("#name").type(users.invalidSub.name);
cy.get("input[type='submit']").click();

cy.contains("Check your input. The format looks wrong.");
cy.get("#id_sub").then(($e) => {
expect($e).to.have.css("border-color", "rgb(222, 12, 12)");
});
thekaveman marked this conversation as resolved.
Show resolved Hide resolved
cy.contains("could not be verified");
});

it("Validates an empty name field", () => {
cy.get("#sub").type(users.invalidSub.sub);
cy.get("input[type='submit']").click();
cy.get("#sub").type(users.eligible.sub);
cy.get("#form-eligibility-verification button[type='submit']").click();

cy.get("input:invalid").should("have.length", 1);
});
Expand Down
32 changes: 21 additions & 11 deletions tests/cypress/specs/feature/rate-limit.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,30 @@ const { eligibility_url, post_confirm } = require("../../plugins/eligibility");

describe("Rate limiting feature spec", () => {
beforeEach(() => {
cy.visit("/" + agencies[0].fields.slug);
cy.visit(eligibility_url);
cy.visit("/");

// agency selection
cy.contains("Choose Your Provider").click();
cy.contains(agencies[0].fields.long_name).click();

// select Courtesy Card
// TODO find a more robust way to do this
cy.get('#form-verifier-selection [type="radio"]').check("2");
cy.get("#form-verifier-selection button[type='submit']").click();
cy.contains("Continue").click();
});

it("Limits excess requests", () => {
const sub = users.invalidSub.sub;
const name = users.invalidSub.name;
const sub = users.ineligible.sub;
const name = users.ineligible.name;

// start by making 5 times as many requests as allowed
const RATE_LIMIT = Cypress.env("DJANGO_RATE_LIMIT");
// these should match the defaults in settings.py
const RATE_LIMIT = 5;
const N_REQUESTS = RATE_LIMIT * 5;
const RATE_LIMIT_PERIOD = 60;

const SUCCESS_STATUS = 302;

// csrfmiddlewaretoken value needed to enable automated form submissions
// there could be multiple forms on the page, so get() the one with the
Expand All @@ -25,11 +38,11 @@ describe("Rate limiting feature spec", () => {
.then((csrfmiddlewaretoken) => {
// make excess requests
for (let i = 0; i < N_REQUESTS; i++) {
// continue on non-200 status codes to check response
// continue on non-successful status codes to check response
post_confirm(csrfmiddlewaretoken, sub, name, false).then((res) => {
if (i < RATE_LIMIT) {
// allow up to API_RATE_LIMIT requests
expect(res.status).to.eq(200);
expect(res.status).to.eq(SUCCESS_STATUS);
} else {
// we've gone beyond API_RATE_LIMIT
expect(res.status).to.eq(400);
Expand All @@ -38,17 +51,14 @@ describe("Rate limiting feature spec", () => {
}

// now wait for the rate limit to refresh
const RATE_LIMIT_PERIOD = parseInt(
Cypress.env("DJANGO_RATE_LIMIT_PERIOD"),
);
// Cypress expects milliseconds, the rate limit period is expressed in seconds
cy.wait(RATE_LIMIT_PERIOD * 1000);

// and try again with a reasonable number of requests
for (let i = 0; i < RATE_LIMIT - 1; i++) {
post_confirm(csrfmiddlewaretoken, sub, name, true).then((res) => {
// these requests should all succeed
expect(res.status).to.eq(200);
expect(res.status).to.eq(SUCCESS_STATUS);
});
}
});
Expand Down