diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..e69de29bb diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..fbf9358b0 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,14 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Explicitly declare text files you want to always be normalized and converted +# to native line endings on checkout. +*.c text +*.h text + +# Declare files that will always have CRLF line endings on checkout. +*.sln text eol=crlf + +# Denote all files that are truly binary and should not be modified. +*.png binary +*.jpg binary diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..e4a5bece1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.11.0a5-alpine + +WORKDIR /src/app/ + +COPY ./requirements.txt . + +RUN ["pip", "install", "-r", "./requirements.txt"] + +COPY . . + +RUN addgroup -S projects && adduser -S -H projects -G projects +RUN chown -R projects:projects /src/app +USER projects diff --git a/README.md b/README.md index d9cca4f37..798fcf313 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Once that completes, also run this command from the same folder. Windows ``` -\venv\Scripts\activate.bat +venv\Scripts\activate.bat ``` macOS & Linux @@ -46,4 +46,4 @@ Every time you want to check your work locally you can type that command, and it ### Previewing Your Work -You can preview your work by running `flask run` in the root of your fork and then visit`http://localhost:5000` in your browser. \ No newline at end of file +You can preview your work by running `flask run` in the root of your fork and then visit`http://localhost:5000` in your browser. diff --git a/pytest.ini b/pytest.ini index d03e9e5d0..ccfcc2693 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,2 +1,2 @@ [pytest] -addopts = --json-report \ No newline at end of file +addopts = -rN --tb=short -p no:warnings diff --git a/requirements.txt b/requirements.txt index 7b72215f2..37f6ff698 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -beautifulsoup4==4.6.3 -Flask==1.0.2 -pytest==3.7.1 -pytest-json-report==0.7.0 -python-dotenv==0.9.1 \ No newline at end of file +beautifulsoup4==4.9.0 +Flask==1.1.2 +pytest==5.4.1 +pytest-json-report==1.2.1 +python-dotenv==0.13.0 diff --git a/tasks.md b/tasks.md index c8c700e51..ca0828c68 100644 --- a/tasks.md +++ b/tasks.md @@ -1,12 +1,10 @@ -# Build a Job Board with Python & Flask +# Setup -## Setup - -### Create Virtual Environment +## Create Virtual Environment In a terminal run the following commands from the root folder of the forked project. -``` shell +``` python -m venv venv ``` @@ -14,162 +12,162 @@ Once that completes, also run this command from the same folder. Windows -``` shell +``` \venv\Scripts\activate.bat ``` macOS & Linux -``` shell +``` source venv/bin/activate ``` Now that you are working in the virtualenv, install the project dependencies with the following command. -``` shell +``` pip install -r requirements.txt ``` -### Verify Setup +## Verify Setup In order to verify that everything is setup correctly, run the following command, which should show you the failing tests. This is good! We’ll be fixing this test once we jump into the build step. -``` shell +``` pytest ``` Every time you want to check your work locally you can type that command, and it will report the status of every task in the project. -### Previewing Your Work +## Previewing Your Work You can preview your work by running `flask run` in the root of your fork. Then visit `http://localhost:5000` in your browser. -## Module 01 - Flask Setup +# Module 01 - Flask Setup -### 1.1 - Import Flask +## 1.1 - Import Flask @pytest.mark.app_import_flask In order to create a flask application, import the `Flask` class and the `render_template` function from `flask` at the top of the `jobs/app.py` file. -### 1.2 - Create a Flask Application +## 1.2 - Create a Flask Application @pytest.mark.app_create_flask_app Still in `app.py` create an instance of the `Flask` class called `app`. Pass the special variable `__name__` to the `Flask` class constructor. -### 1.3 - Templates Folder +## 1.3 - Templates Folder @pytest.mark.templates_folder Create a folder called `templates` in the `jobs` directory. -### 1.4 - Create Index Template +## 1.4 - Create Index Template @pytest.mark.index_template In the root of the `templates` folder, create a file called `index.html`. Add a single line to the file: - `

Jobs

` -### 1.5 - Index Route Function +## 1.5 - Index Route Function -@pytest.mark.app_index_route_function The homepage of our job board will display all of the jobs in our database. +@pytest.mark.app_index_route_function The homepage of our job board will display all of the jobs in our database. -For now let’s setup a basic route that displays our simplified `index.html` template. +For now let’s setup a basic route that displays our simplified `index.html` template. -- Create a basic route in `app.py` by creating a function called `jobs`. +- Create a basic route in `app.py` by creating a function called `jobs`. - In the body of the function return a call to the `render_template()` function, pass a parameter of `index.html`. -### 1.6 - Route Decorators +## 1.6 - Route Decorators @pytest.mark.app_route_decoractors Still in `app.py`: -- Attach a `route()` decorator with the URL of `/` to the `jobs` function. -- Attach an additional route decorator of `/jobs`. +- Attach a `route()` decorator with the URL of `/` to the `jobs` function. +- Attach an additional route decorator of `/jobs`. **Note: The `jobs` function can now be reached at `/` and `/jobs`** -### Preview Module 1 - +**Preview** + At this point you have a working application with a single route. Try it out: - Open a terminal at the root of the project -- Run the command `flask run`. -- Open a browser and navigate to the URL: `http://localhost:5000`. +- Run the command `flask run`. +- Open a browser and navigate to the URL: `http://localhost:5000`. **Note: Appending `/jobs` should display the same page.** -## Module 02 - Base Template and Styling +# Module 02 - Base Template and Styling -### 2.1 - Create a Layout Template +## 2.1 - Create a Layout Template -@pytest.mark.layout_template We want each template to have a consistent look and feel. We can create a base layout that all templates can extend. +@pytest.mark.layout_template We want each template to have a consistent look and feel. We can create a base layout that all templates can extend. Create a new file called `layout.html` in the root of the `templates` folder. Next, copy the basic structure of this file from the file called `templates.html`. -### 2.2 - Add the Bulma CSS Framework +## 2.2 - Add the Bulma CSS Framework -@pytest.mark.add_bulma_css_framework The app will be styled with the [Bulma CSS Framework](bulma.io) and icons will be provided by [FontAwesome](fontawesome.com). +@pytest.mark.add_bulma_css_framework The app will be styled with the [Bulma CSS Framework](bulma.io) and icons will be provided by [FontAwesome](fontawesome.com). Add a `` tag to the head of `layout.html`. Give it an attribute of `rel="stylesheet"`. For the `href` use the mustache template markup `{{}}` and the flask `url_for()` function to construct a link for the file `css/bulma.min.css` in `static` folder. **Hint: use the keyword argument `filename`**. -### 2.3 - Add Custom CSS +## 2.3 - Add Custom CSS -@pytest.mark.add_custom_css For the second `` tag in `layout.html` construct an `href` for the file `css/app.css`, also in the `static` folder, using the same method. Don't forget the `rel` attribute. +@pytest.mark.add_custom_css For the second `` tag in `layout.html` construct an `href` for the file `css/app.css`, also in the `static` folder, using the same method. Don't forget the `rel` attribute. -### 2.4 - Add FontAwesome +## 2.4 - Add FontAwesome -@pytest.mark.add_fontawesome The last `` tag in `layout.html` should have an `href` value of `https://use.fontawesome.com/releases/v5.2.0/css/all.css`. Make sure to preview the application and check out the _awesome_ styling. +@pytest.mark.add_fontawesome The last `` tag in `layout.html` should have an `href` value of `https://use.fontawesome.com/releases/v5.2.0/css/all.css`. Make sure to preview the application and check out the _awesome_ styling. -### 2.5 - Extend Base Template +## 2.5 - Extend Base Template @pytest.mark.extend_base_template To use `layout.html` as the base template: - Open `index.html`, above the `

` use the template markup `{% %}` and the extends tag to inherit `layout.html`. -### Preview Module 2 - -At this point you have a styled application. Check out the styles: +**Preview** +At this point you have a styled application. Check out the styles: + - Open a terminal at the root of the project -- Run the command `flask run`. +- Run the command `flask run`. - Open a browser and navigate to the URL: `http://localhost:5000`. -## Module 03 - Preparing for Dynamic Content +# Module 03 - Preparing for Dynamic Content -### 3.1 - Import SQLite +## 3.1 - Import SQLite @pytest.mark.app_import_sqlite At the top of `app.py` import the built_in `sqlite3` library. -### 3.2 - Import Global Namespace +## 3.2 - Import Global Namespace -@pytest.mark.app_import_g To provide access to the database throughout the application import the global helper `g` from `flask`. **Hint: the `from` statement already exists add `g` to the `import` comma separated list.** +@pytest.mark.app_import_g To provide access to the database throughout the application import the global helper `g` from `flask`. **Hint: the `from` statement already exists add `g` to the `import` comma separated list.** -### 3.3 - Database Path +## 3.3 - Database Path @pytest.mark.app_db_path Below all of the import statements, create a constant called `PATH`, that contains the path to the already created database stored in `db/jobs.sqlite`. -### 3.4 - Global Database Attribute +## 3.4 - Global Database Attribute -@pytest.mark.app_open_connection_get_attribute At the top of `app.py` create a function called `open_connection`. +@pytest.mark.app_open_connection_get_attribute At the top of `app.py` create a function called `open_connection`. -In the body of the `open_connection` function use the built_in `getattr()` function to get the `'_connection'` attribute from the `g` object, and set the default to `None`. Assign the return value of the `getattr` function to `db`. +In the body of the `open_connection` function use the built_in `getattr()` function to get the `'_connection'` attribute from the `g` object, and set the default to `None`. Assign the return value of the `getattr` function to `db`. -### 3.5 - Global Database Connection +## 3.5 - Global Database Connection @pytest.mark.app_open_connection_connection Still in the `open_connection` function, test if `connection` is `None` if it is, set `connection` and `g._connection` to `sqlite3.connect(PATH)` using multiple assignment. -### 3.6 - sqlite3 Row Factory +## 3.6 - sqlite3 Row Factory @pytest.mark.app_open_connection_row_factory To make accessing data easier, after the if statement in `open_connection`: - Set the row_factory of `connection` to `sqlite3.Row`. **Note: All rows returned from the database will be named tuples.** - Return the `connection` variable. -### 3.7 - Query Database Function +## 3.7 - Query Database Function -@pytest.mark.app_execute_sql Let’s create a function to make it easier to query the database. +@pytest.mark.app_execute_sql Let’s create a function to make it easier to query the database. Below the `open_connection` function in `app.py` create a function called `execute_sql`. In the body of `execute_sql` create a variable called `db`. Assign this variable the return value of a call to the newly created `open_connection` function. -### 3.8 - Query Database Function Parameters +## 3.8 - Query Database Function Parameters @pytest.mark.app_execute_sql_parameters Still working with the `execute_sql` function: @@ -178,11 +176,11 @@ In the body of `execute_sql` create a variable called `db`. Assign this variable - Set the default of `commit` to `False`. - Set the default of `single` to `False`. -### 3.9 - Query Database Function Execute +## 3.9 - Query Database Function Execute @pytest.mark.app_execute_sql_execute In the body of `execute_sql` call the `execute` function on `connection`, pass in the `sql` and `values` variables. Assign the return value to a variable called `cursor`. -### 3.10 - Query Database Function Commit +## 3.10 - Query Database Function Commit @pytest.mark.app_execute_sql_commit In the body of `execute_sql`: @@ -192,7 +190,7 @@ In the body of `execute_sql` create a variable called `db`. Assign this variable - Close the cursor. - Return `results` variable. -### 3.11 - Close the Connection +## 3.11 - Close the Connection @pytest.mark.app_close_connection In order to make sure the database connection is closed when the `app_context` is torn down: @@ -201,155 +199,156 @@ In the body of `execute_sql` create a variable called `db`. Assign this variable In the function body: -- Call `getattr` with three arguments `g`, `'_connection'`, and `None` -- Assign the return value to a `connection` variable. -- If `connection` is not `None` `close` the `connection`. +- Call `getattr` with three arguments `g`, `'_connection'`, and `None` +- Assign the return value to a `connection` variable. +- If `connection` is not `None` `close` the `connection`. -### 3.12 - Close the Connection Decorator +## 3.12 - Close the Connection Decorator @pytest.mark.app_close_connection_decorator To ensure the `close_connection` function is called when the `app_context` is destroyed decorate it with `@app.teardown_appcontext`. -## Module 04 - Display All Jobs +# Module 04 - Display All Jobs -### 4.1 - Template Macros +## 4.1 - Template Macros -@pytest.mark.template_macros In the template folder create a new file called `_macros.html`. +@pytest.mark.template_macros In the template folder create a new file called `_macros.html`. -### 4.2 - Show Job Macro Definition +## 4.2 - Show Job Macro Definition @pytest.mark.show_job_macro_definition In `_macros.html` create a template macro, using the `macro` tag, called `show_job`. `show_job` should take one parameter called `job`. Don't forgot to end the macro. -### 4.3 - Show Job Macro HTML +## 4.3 - Show Job Macro HTML -@pytest.mark.show_job_macro_html Locate the `template.html` file in the root of the project. Open it and find the code labeled ``. Copy the code to the body of the `show_job` macro in `_macros.html`. +@pytest.mark.show_job_macro_html Locate the `template.html` file in the root of the project. Open it and find the code labeled +``. Copy the code to the body of the `show_job` macro in `_macros.html`. -### 4.4 - Show Job Macro Header +## 4.4 - Show Job Macro Header @pytest.mark.show_job_macro_header Still in the body of the `show_job` macro in `_macros.html` find the `

` tag with a class of `card_header_title`. -- Add an `` tag with an `href` of `{{ url_for('job', job_id=job['id']) }}`. -- The content should be `{{ job['title'] }}`. +- Add an `` tag with an `href` of `{{ url_for('job', job_id=job['id']) }}`. +- The content should be `{{ job['title'] }}`. -### 4.5 - Show Job Macro Body +## 4.5 - Show Job Macro Body -@pytest.mark.show_job_macro_body Next find the `

` with a class of `content` in the `show_job` macro and add a `

` tag. +@pytest.mark.show_job_macro_body Next find the `

` with a class of `content` in the `show_job` macro and add a `

` tag. In `

` tag add the following: -- `` tag with an `href` of `{{ url_for('employer', employer_id=job['employer_id']) }}`. The content should be `{{ job['employer_name'] }}`. +- `` tag with an `href` of `{{ url_for('employer', employer_id=job['employer_id']) }}`. The content should be `{{ job['employer_name'] }}`. - Line break -- ${{ job['salary'] }} +- ${{ job['salary'] }} - Line break - {{ job['description'] }} -### 4.6 - Show Jobs Macro Definition +## 4.6 - Show Jobs Macro Definition @pytest.mark.show_jobs_macro_definition In `_macros.html` create a template macro using the `macro` tag call it `show_jobs`. `show_jobs` should take one parameter called `jobs`. Don't forgot to end the macro. -### 4.7 - Show Jobs Macro For Loop +## 4.7 - Show Jobs Macro For Loop @pytest.mark.show_jobs_macro_for_loop Still in `_macros.html` and in the body of the `show_jobs` macro add the following HTML: - Add a `

` with two classes `columns` and `is-multiline`. - In this `
` add a `for in` loop that loops through all jobs. **Note: Use the `{% %}` template syntax, don’t forget about ending the `for` loop.** -### 4.8 - Show Jobs Macro For Loop Body +## 4.8 - Show Jobs Macro For Loop Body @pytest.mark.show_jobs_macro_for_loop_body In the body of the `for` loop add a `
` with two classes `column` and `is_half`. - In this `column` `
` add a call to the `show_job` macro passing in an individual `job` from the `for` loop. -### 4.9 - Import Macros +## 4.9 - Import Macros -@pytest.mark.import_macros In `templates/layouts.html` import the `show_job`, and `show_jobs` macros using the following code: +@pytest.mark.import_macros In `templates/layout.html` import the `show_job`, and `show_jobs` macros using the following code: -``` python +``` {% from '_macros.html' import show_job, show_jobs with context %} ``` -**Notes: Because each template extends `layouts.html` all of them will have access to these two new macros.** +**Notes: Because each template extends `layout.html` all of them will have access to these two new macros.** -### 4.10 - Index Template +## 4.10 - Index Template @pytest.mark.index_template Copy the HTML structure of the `index.html` file from `templates.html`. Replace the `

` with the copied HTML structure. -### 4.11 - Display All Jobs +## 4.11 - Display All Jobs @pytest.mark.display_all_jobs In the `index.html` template above the `{% endblock %}` add a call to the `show_jobs` macro passing in the argument `jobs`. -### 4.12 - Gather All Jobs +## 4.12 - Gather All Jobs -@pytest.mark.app_jobs_route_jobs In `app.py` locate the `jobs` function. +@pytest.mark.app_jobs_route_jobs In `app.py` locate the `jobs` function. Above the `render_template` function, call the `execute_sql` function: -- Pass in the SQL statement: `'SELECT job.id, job.title, job.description, job.salary, employer.id as employer_id, employer.name as employer_name FROM job JOIN employer ON employer.id = job.employer_id'`. -- Assign the results of the call to a variable called `jobs`. +- Pass in the SQL statement: `'SELECT job.id, job.title, job.description, job.salary, employer.id as employer_id, employer.name as employer_name FROM job JOIN employer ON employer.id = job.employer_id'`. +- Assign the results of the call to a variable called `jobs`. - In the `render_template` function, pass a keyword argument of `jobs=jobs`. -### Preview Module 4 +**Preview** At this point you can see all jobs on the homepage: - Open a terminal at the root of the project -- Run the command `flask run`. -- Open a browser and navigate to the URL: `http://localhost:5000`. +- Run the command `flask run`. +- Open a browser and navigate to the URL: `http://localhost:5000`. **Note: Appending `/jobs` should display the same page.** -## Module 05 - Display Individual Jobs +# Module 05 - Display Individual Jobs -### 5.1 - Job Template +## 5.1 - Job Template -@pytest.mark.app_job_template We need a template to display an individual job. Create a new file called `job.html` in the `template` folder. +@pytest.mark.app_job_template We need a template to display an individual job. Create a new file called `job.html` in the `template` folder. -In the file use an `extends` template tag to inherit `layout.html`. +In the file use an `extends` template tag to inherit `layout.html`. After the `extends` tag add a template `block` called `content`. In the block call the `show_job` macro passing in `job`. **Note: Use the `{{}}` for the macro call.** -### 5.2 - Job Route Function +## 5.2 - Job Route Function @pytest.mark.app_job_route In `app.py` create a function called `job`. In the body return a call to the `render_template` function passing in the newly created `job.html` template. -### 5.3 - Job Route Decorator +## 5.3 - Job Route Decorator -@pytest.mark.app_job_route_decorator We only need one job from the database, we will use the `execute_sql` function passing in a query with a where clause. In the where clause we will need a `job_id`. We are going to get this from the URL. +@pytest.mark.app_job_route_decorator We only need one job from the database, we will use the `execute_sql` function passing in a query with a where clause. In the where clause we will need a `job_id`. We are going to get this from the URL. -Still in `app.py`, add a route decorator with the URL path `/job/` to the `job` function. +Still in `app.py`, add a route decorator with the URL path `/job/` to the `job` function. -### 5.4 - Job Route Parameter +## 5.4 - Job Route Parameter -@pytest.mark.app_job_route_parameter To use the `job_id`, received from the URL, we need to pass it to the `job` function. Add `job_id` to the parameter list of the `job` function. +@pytest.mark.app_job_route_parameter To use the `job_id`, received from the URL, we need to pass it to the `job` function. Add `job_id` to the parameter list of the `job` function. -### 5.5 - Job Route Data +## 5.5 - Job Route Data -@pytest.mark.app_job_route_data In the `job` function, above the `render_template` function, call the `execute_sql` function and assign the results of the call to a `job` variable. +@pytest.mark.app_job_route_data In the `job` function, above the `render_template` function, call the `execute_sql` function and assign the results of the call to a `job` variable. Pass these three arguments to `execute_sql`: - SQL Query: `'SELECT job.id, job.title, job.description, job.salary, employer.id as employer_id, employer.name as employer_name FROM job JOIN employer ON employer.id = job.employer_id WHERE job.id = ?'` - List Literal: [job_id] - single=True, This will bring back only one result. -### 5.6 - Job Route Pass Data +## 5.6 - Job Route Pass Data @pytest.mark.app_job_route_pass_data The template needs access to the job data. Let's pass the newly created variable `job` to the `render_template` function. This is done using the keyword argument syntax `job=job`. -### Preview Module 5 - +**Preview** + At this point you can see an individual job: - Open a terminal at the root of the project -- Run the command `flask run`. -- Open a browser and navigate to the URL: `http://localhost:5000/job/1`. +- Run the command `flask run`. +- Open a browser and navigate to the URL: `http://localhost:5000/job/1`. -## Module 06 - Display Individual Employers +# Module 06 - Display Individual Employers -### 6.1 - Employer Template +## 6.1 - Employer Template -@pytest.mark.employer_template To display an employer create a new file called `employer.html` in the `templates` folder. Open `templates.html`, find the appropriate block of HTML and copy and paste it to `employer.html`. +@pytest.mark.employer_template To display an employer create a new file called `employer.html` in the `templates` folder. Open `templates.html`, find the appropriate block of HTML and copy and paste it to `employer.html`. To the top of the file inherit from the `layout.html` template by using an `extends` template tag. -### 6.2 - Employer Template Details +## 6.2 - Employer Template Details @pytest.mark.employer_template_details Still in `employer.html` as the first thing in the template block add the following HTML: @@ -358,25 +357,25 @@ To the top of the file inherit from the `layout.html` template by using an `exte - Nested in the `
` add a `
` with a class of `description` - Nested in the description `
` add a`

` with the content {{ employer['description'] }} -### 6.3 - Employer Template All Jobs +## 6.3 - Employer Template All Jobs @pytest.mark.employer_template_all_jobs Below the `

` Jobs header in `employer.html` add a call to the `show_jobs` macro passing in `jobs`. -### 6.4 - Employer Template Reviews +## 6.4 - Employer Template Reviews -@pytest.mark.employer_template_reviews Still in `employer.html` find the review `

`, remove the comment surrounding the empty `{% %}` template tag. To this tag add a `for in` loop to loop through all `reviews`. Add the `endfor` directive to the second empty `{% %}` template tag, don't forget to the remove the comment. +@pytest.mark.employer_template_reviews Still in `employer.html` find the review `

`, remove the comment surrounding the empty `{% %}` template tag. To this tag add a `for in` loop to loop through all `reviews`. Add the `endfor` directive to the second empty `{% %}` template tag, don't forget to the remove the comment. -### 6.5 - Employer Template Review Stars +## 6.5 - Employer Template Review Stars @pytest.mark.employer_template_review_stars Still `employer.html` in the `
` with a class of `media_left` add this for loop: -``` python +``` {% for _ in range(1, review['rating']): %} {% endfor %} ``` -### 6.6 - Employer Template Review Details +## 6.6 - Employer Template Review Details @pytest.mark.employer_template_review_details Still in `employer.html` in the `content
` add a paragraph tag. In the paragraph display the details of a review: @@ -385,17 +384,17 @@ To the top of the file inherit from the `layout.html` template by using an `exte - `date` (Recommend Style: ``) - `review` -### 6.7 - Employer Route +## 6.7 - Employer Route -@pytest.mark.app_employer_route The template we have just built needs access to employer, job, and review data. Let's create a new function in `app.py` called `employer`. +@pytest.mark.app_employer_route The template we have just built needs access to employer, job, and review data. Let's create a new function in `app.py` called `employer`. Add a route decorator with a URL pattern of `/employer/`. In the body return a call to the `render_template` function passing in the `employer.html` template. -### 6.8 - Employer Route Employer Details +## 6.8 - Employer Route Employer Details -@pytest.mark.app_employer_route_employers Still working with the `employer` function add `employer_id` to the parameter list so that we have access to this value. Above the `render_template` function make a call to `execute_sql` and assign the return value to `employer`. +@pytest.mark.app_employer_route_employers Still working with the `employer` function add `employer_id` to the parameter list so that we have access to this value. Above the `render_template` function make a call to `execute_sql` and assign the return value to `employer`. Pass the following arguments to `execute_sql`: @@ -405,16 +404,16 @@ Pass the following arguments to `execute_sql`: In the `render_template` function, pass a keyword argument of `employer=employer`. -### 6.9 - Employer Route Employer Jobs +## 6.9 - Employer Route Employer Jobs -@pytest.mark.app_employer_route_jobs On the employer details page, we want to display all of the employers' jobs. In the `employer` function in `app.py` below the `employer` variable, add a call to the `execute_sql` function and assign the results to a variable called `jobs`. Pass the function two arguments: +@pytest.mark.app_employer_route_jobs On the employer details page, we want to display all of the employers' jobs. In the `employer` function in `app.py` below the `employer` variable, add a call to the `execute_sql` function and assign the results to a variable called `jobs`. Pass the function two arguments: - SQL Query: `'SELECT job.id, job.title, job.description, job.salary FROM job JOIN employer ON employer.id = job.employer_id WHERE employer.id = ?'` - List Literal: [employer_id] In the `render_template` function, add another keyword argument of `jobs=jobs` -### 6.10 - Employer Route Employer Review +## 6.10 - Employer Route Employer Review @pytest.mark.app_employer_route_reviews Still in the `employer` function in `app.py` below the jobs query add a new query to get all review for the employer. Make a call to `execute_sql` and assign the return value to `reviews`. Pass in the arguments: @@ -423,17 +422,17 @@ In the `render_template` function, add another keyword argument of `jobs=jobs` In the `render_template` function, add another keyword argument of `reviews=reviews` -### Preview - +**Preview** + At this point you can see an individual employer: - Open a terminal at the root of the project -- Run the command `flask run`. -- Open a browser and navigate to the URL: `http://localhost:5000/employer/1`. +- Run the command `flask run`. +- Open a browser and navigate to the URL: `http://localhost:5000/employer/1`. -## Module 07 - Employer Reviews +# Module 07 - Employer Reviews -### 7.1 - Review Template +## 7.1 - Review Template @pytest.mark.review_template To display a review form, create a new file called `review.html` in the templates folder. Open `templates.html`, find the appropriate block of HTML and copy and paste it to `review.html`. @@ -441,30 +440,36 @@ Inherit from the `layout.html` template by using an `extends` template tag. Find the cancel anchor tag. Add an `href` attribute with a value of `{{ url_for('employer', employer_id=employer_id) }}`. -### 7.2 - Review Route +## 7.2 - Review Route -@pytest.mark.app_review_route In `app.py` below the `employer` function create a new function called `review`. Add `employer_id` to the parameter list. +@pytest.mark.app_review_route In `app.py` below the `employer` function create a new function called `review`. Add `employer_id` to the parameter list. Add a route decorator with a URL pattern of `/employer//review`. Also add a keyword argument `methods` set to a tuple with two values: `'GET'` and `'POST'`. In the body of the function return the `render_template` function passing in the `review.html` template and a keyword argument of `employer_id=employer_id`. -### 7.3 - POST Request Check +## 7.3 - POST Request Check -@pytest.mark.app_review_post_request_check In the body of the `review` above the `render_template` function call, create an `if` statement that checks if `request.method` is equal to `'POST'`. +@pytest.mark.app_review_post_request_check In the body of the `review` above the `render_template` function call, create an `if` statement that checks if `request.method` is equal to `'POST'`. -- In the `if` statement create four variables `review`, `rating`, `title`, and `status`. Set them equal to their respective `request.form` values i.e. `request.form['review']`. +- In the `if` statement create four variables `review`, `rating`, `title`, and `status`. Set them equal to their respective `request.form` values i.e. `request.form['review']`. - Create a `date` variable assign it todays date formatted like '08/10/2018'. **Hint: Use `now()` and `strftime("%m/%d/%Y")`. If you use `now()` add an `import datetime` statement to the top of `app.py`.** -### 7.4 - Insert Review +## 7.4 - Insert Review + +@pytest.mark.app_review_insert_review Still in the `review` function below the variables in the `if` statement, call the `execute_sql` function with the following arguments: + +- 'INSERT INTO review (review, rating, title, date, status, employer_id) VALUES (?, ?, ?, ?, ?, ?)' +- (review, rating, title, date, status, employer_id) +- commit=True. -@pytest.mark.app_review_insert_review Still in the `review` function below the variables in the `if` statement, connect to the database, insert the form values, and commit the changes. Follow these steps: +## 7.5 - Redirect to Employer Page -- Assign a `db` variable to a call to `open_connection()` -- `execute` the following SQL statement on `db`: `'INSERT INTO review (review, rating, title, date, status, employer_id) VALUES (?, ?, ?, ?, ?, ?)'` passing the values: `(review, rating, title, date, status, employer_id)` -- `commit` the changes to the database. -- Return a redirect taking the user back to the employer page. **Hint: use `redirect()` and `url_for()` (pass a keyword argument of `employer_id=employer_id`) both of which need to be imported from flask.** +@pytest.mark.app_redirect_to_employer At the end of the function return the user back to the employer page. +This can be done by using the `redirect` and `url_for` functions. +To start import both of these functions from `flask`. Next return a call to `redirect` and pass in a call to the `url_for` function. +Pass the `url_for` function the route to redirect to which is the `employer` route and a keyword argument of `employer_id=employer_id`. -### 7.5 - Employer Review Button +## 7.6 - Employer Review Button @pytest.mark.employer_review_button Open the `employer.html` template and find the anchor tag to create a review. Add an `href` attribute with a value of `{{ url_for('review', employer_id=employer['id']) }}`. diff --git a/tests/test_module1.py b/tests/test_module1.py index 645d83c70..7cdcaca1e 100644 --- a/tests/test_module1.py +++ b/tests/test_module1.py @@ -4,39 +4,92 @@ from .utils import * from jobs import app + @pytest.mark.test_app_import_flask_module1 def test_app_import_flask_module1(): - assert 'Flask' in dir(app), 'Have you imported the `Flask` class from `flask`?' - assert inspect.isclass(app.Flask), '`Flask` is not a class.' - assert 'render_template' in dir(app), '`render_template` has not been imported.' - assert inspect.isfunction(app.render_template), '`render_template` is not a function.' + flask_exists = "Flask" in dir(app) + assert flask_exists, "Have you imported the `Flask` class from `flask`?" + + flask_is_class = inspect.isclass(app.Flask) + assert flask_is_class, "`Flask` is not a class." + + render_template_exists = "render_template" in dir(app) + assert render_template_exists, "`render_template` has not been imported." + + render_template_is_function = inspect.isfunction(app.render_template) + assert render_template_is_function, "`render_template` is not a function." + @pytest.mark.test_app_create_flask_app_module1 def test_app_create_flask_app_module1(): - assert 'app' in dir(app), 'Have you created an instance of the `Flask` class called `app`?' - assert isinstance(app.app, app.Flask), '`app` is not an instance of the `Flask` class.' + flask_app = "app" in dir(app) + assert flask_app, "Have you created an instance of the `Flask` class called `app`?" + + flask_instance = isinstance(app.app, app.Flask) + assert flask_instance, "`app` is not an instance of the `Flask` class." + @pytest.mark.test_index_template_module1 def test_index_template_module1(): - assert os.path.isdir('jobs/templates'), 'The `templates` folder has not been created.' - assert template_exists('index'), 'The `index.html` template does not exist in the `templates` folder.' - assert template_find('index', 'h1', limit=1), "The `

` in the `index.html` template does not contain the contents 'Jobs'." - assert template_find('index', 'h1', limit=1)[0].text == 'Jobs', "The `

` in the `index.html` template does not contain the contents 'Jobs'." + template_dir = os.path.isdir("jobs/templates") + assert template_dir, "The `templates` folder has not been created." + + index_exists = template_exists("index") + assert ( + index_exists + ), "The `index.html` template does not exist in the `templates` folder." + + h1_exists = template_find("index", "h1", limit=1) + assert ( + h1_exists + ), "The `

` in the `index.html` template does not contain the contents 'Jobs'." + + h1_jobs = template_find("index", "h1", limit=1)[0].text == "Jobs" + assert ( + h1_jobs + ), "The `

` in the `index.html` template does not contain the contents 'Jobs'." + @pytest.mark.test_app_index_route_function_module1 def test_app_index_route_function_module1(): - assert 'app' in dir(app), 'Have you created an instance of the `Flask` class called `app`?' - assert 'jobs' in dir(app), 'Have you created the `jobs` function?' - result = [item for item in get_functions(app.jobs) if item.startswith('render_template:index.html')] - assert len(result) == 1, 'Have you called the `render_template` function.' + flask_app = "app" in dir(app) + assert flask_app, "Have you created an instance of the `Flask` class called `app`?" + + jobs_function = "jobs" in dir(app) + assert jobs_function, "Have you created the `jobs` function?" + + result = [ + item + for item in get_functions(app.jobs) + if item.startswith("render_template:index.html") + ] + result_len = len(result) == 1 + assert result_len, "Have you called the `render_template` function." + + return_values = get_functions_returns(app.jobs)[0] + return_exists = ( + return_values["value/args/s"] == "index.html" + and return_values["value/func/id"] == "render_template" + ) + assert return_exists, "Did you return the `render_template` call?" + @pytest.mark.test_app_route_decoractors_module1 def test_app_route_decoractors_module1(): - assert 'app' in dir(app), 'Have you created an instance of the `Flask` class called `app`?' - assert template_exists('index'), 'The `index.html` template does not exist in the `templates` folder.' - assert 'jobs' in dir(app), 'Have you created the `jobs` function?' + flask_app = "app" in dir(app) + assert flask_app, "Have you created an instance of the `Flask` class called `app`?" + + index_exists = template_exists("index") + assert ( + index_exists + ), "The `index.html` template does not exist in the `templates` folder." + + jobs_function = "jobs" in dir(app) + assert jobs_function, "Have you created the `jobs` function?" rules = list_routes(app.app) + root_route = "jobs:GET,HEAD,OPTIONS:/" in rules + assert root_route, "Have you decorated the `jobs` function with the `/` route?" - assert 'jobs:GET,HEAD,OPTIONS:/' in rules, 'Have you decorated the `jobs` function with the `/` route?' - assert 'jobs:GET,HEAD,OPTIONS:/jobs' in rules, 'Have you decorated the `jobs` function with the `/jobs` route?' + jobs_route = "jobs:GET,HEAD,OPTIONS:/jobs" in rules + assert jobs_route, "Have you decorated the `jobs` function with the `/jobs` route?" diff --git a/tests/test_module2.py b/tests/test_module2.py index fe9d7f0ba..a1a5eeae3 100644 --- a/tests/test_module2.py +++ b/tests/test_module2.py @@ -3,33 +3,68 @@ from jobs import app from .utils import * -calls = template_functions('layout', 'url_for') +calls = template_functions("layout", "url_for") + @pytest.mark.test_layout_template_module2 def test_layout_template_module2(): - assert template_exists('layout'), 'The `layout.html` template does not exist in the `templates` folder.' + layout_exists = template_exists("layout") + assert ( + layout_exists + ), "The `layout.html` template does not exist in the `templates` folder." + @pytest.mark.test_add_bulma_css_framework_module2 def test_add_bulma_css_framework_module2(): - assert template_exists('layout'), 'The `layout.html` template does not exist in the `templates` folder.' - assert 'static:filename:css/bulma.min.css' in calls, 'Looks like `bulma.min.css` is not linked in `layout.html`.' + layout_exists = template_exists("layout") + assert ( + layout_exists + ), "The `layout.html` template does not exist in the `templates` folder." + + css_exists = "static:filename:css/bulma.min.css" in calls + assert css_exists, "Looks like `bulma.min.css` is not linked in `layout.html`." + @pytest.mark.test_add_custom_css_module2 def test_add_custom_css_module2(): - assert template_exists('layout'), 'The `layout.html` template does not exist in the `templates` folder.' - assert 'static:filename:css/app.css' in calls, 'Looks like `app.css` is not linked in `layout.html`.' + layout_exists = template_exists("layout") + assert ( + layout_exists + ), "The `layout.html` template does not exist in the `templates` folder." + + css_exists = "static:filename:css/app.css" in calls + assert css_exists, "Looks like `app.css` is not linked in `layout.html`." + @pytest.mark.test_add_fontawesome_module2 def test_add_fontawesome_module2(): - assert template_exists('layout'), 'The `layout.html` template does not exist in the `templates` folder.' + layout_exists = template_exists("layout") + assert ( + layout_exists + ), "The `layout.html` template does not exist in the `templates` folder." + attr = { - 'href': 'https://use.fontawesome.com/releases/v5.2.0/css/all.css', - 'rel': 'stylesheet' + "href": "https://use.fontawesome.com/releases/v5.2.0/css/all.css", + "rel": "stylesheet", } - assert template_soup('layout').find('link', attr), 'Looks like FontAwesome is not linked in `layout.html`.' + layout_link_exists = template_soup("layout").find("link", attr) + assert layout_link_exists, "Looks like FontAwesome is not linked in `layout.html`." + @pytest.mark.test_extend_base_template_module2 def test_extend_base_template_module2(): - assert template_exists('index'), 'The `index.html` template does not exist in the `templates` folder.' - assert template_exists('layout'), 'The `layout.html` template does not exist in the `templates` folder.' - assert 'layout.html' in template_extends('index'), 'The `index.html` template does not extend `layout.html`.' + index_exists = template_exists("index") + assert ( + index_exists + ), "The `index.html` template does not exist in the `templates` folder." + + layout_exists = template_exists("layout") + assert ( + layout_exists + ), "The `layout.html` template does not exist in the `templates` folder." + + index_extends = "layout.html" in template_extends("index") + assert index_extends, "The `index.html` template does not extend `layout.html`." + + content_block = "content" in template_block("index") + assert content_block, "Have you added a template `block` called `content`?" diff --git a/tests/test_module3.py b/tests/test_module3.py index d64986d31..e0cc62461 100644 --- a/tests/test_module3.py +++ b/tests/test_module3.py @@ -4,82 +4,197 @@ from jobs import app from .utils import * + @pytest.mark.test_app_import_sqlite_module3 def test_app_import_sqlite_module3(): - assert 'sqlite3' in dir(app), 'Have you imported `sqlite`?' + sqlite_import = "sqlite3" in dir(app) + assert sqlite_import, "Have you imported `sqlite`?" + @pytest.mark.test_app_import_g_module3 def test_app_import_g_module3(): - assert 'g' in dir(app), 'Have you imported the `g` class from `flask`?' + g_import = "g" in dir(app) + assert g_import, "Have you imported the `g` class from `flask`?" + @pytest.mark.test_app_db_path_module3 def test_app_db_path_module3(): - assert 'PATH' in dir(app), 'Have you created a constant called `PATH`.' - assert app.PATH == 'db/jobs.sqlite', 'Have you created a constant called `PATH`?' + assert "PATH" in dir(app), "Have you created a constant called `PATH`?" + assert app.PATH == "db/jobs.sqlite", "Have you created a constant called `PATH`?" + @pytest.mark.test_app_open_connection_get_attribute_module3 def test_app_open_connection_get_attribute_module3(): - assert 'open_connection' in dir(app), 'Have you defined a function named `open_connection`.' - assert 'getattr:g:_connection:None' in get_functions(app.open_connection), 'Have you used the `getattr` function to get the global `_connection`?' + + open_connection = "open_connection" in dir(app) + assert open_connection, "Have you defined a function named `open_connection`?" + + result = [ + item + for item in get_functions(app.open_connection) + if item.startswith("getattr:g:_connection") + ] + result_len = len(result) == 1 + assert ( + result_len + ), "Have you used the `getattr` function to get the global `_connection`?" + @pytest.mark.test_app_open_connection_connection_module3 def test_app_open_connection_connection_module3(): - assert 'g' in dir(app), 'Have you imported the `g` class from `flask`?' - assert 'app' in dir(app), 'Have you created an instance of the `Flask` class called `app`?' - assert 'open_connection' in dir(app), 'Have you defined a function named `open_connection`.' + g_import = "g" in dir(app) + assert g_import, "Have you imported the `g` class from `flask`?" + + flask_app = "app" in dir(app) + assert flask_app, "Have you created an instance of the `Flask` class called `app`?" + + open_connection = "open_connection" in dir(app) + assert open_connection, "Have you defined a function named `open_connection`?" + with app.app.app_context(): app.open_connection() - assert hasattr(app.g, '_connection'), 'Did you assign the `_connection` attribute to `g`?' - _, _, db_name = app.g._connection.execute('PRAGMA database_list').fetchone() - assert os.path.join(os.getcwd(), 'db', 'jobs.sqlite') == db_name, 'Did you pass the `connect` function the `PATH` constant?' + + connection_exists = hasattr(app.g, "_connection") + assert connection_exists, "Did you assign the `_connection` attribute to `g`?" + + _, _, db_name = app.g._connection.execute("PRAGMA database_list").fetchone() + db_exists = os.path.join(os.getcwd(), "db", "jobs.sqlite") == db_name + assert db_exists, "Did you pass the `connect` function the `PATH` constant?" + @pytest.mark.test_app_open_connection_row_factory_module3 def test_app_open_connection_row_factory_module3(): - assert 'g' in dir(app), 'Have you imported the `g` class from `flask`?' - assert 'app' in dir(app), 'Have you created an instance of the `Flask` class called `app`?' - assert 'open_connection' in dir(app), 'Have you defined a function named `open_connection`.' + g_import = "g" in dir(app) + assert g_import, "Have you imported the `g` class from `flask`?" + + flask_app = "app" in dir(app) + assert flask_app, "Have you created an instance of the `Flask` class called `app`?" + + open_connection = "open_connection" in dir(app) + assert open_connection, "Have you defined a function named `open_connection`?" + with app.app.app_context(): db = app.open_connection() - assert isinstance(db, app.sqlite3.Connection), 'Are you returning the database connection?' - assert id(db.row_factory) == id(app.sqlite3.Row), 'Have you set the database `row_factory` to the sqlite3.Row class?' + return_connection = isinstance(db, app.sqlite3.Connection) + assert return_connection, "Are you returning the database connection?" + + row_factory = id(db.row_factory) == id(app.sqlite3.Row) + assert ( + row_factory + ), "Have you set the database `row_factory` to the sqlite3.Row class?" + @pytest.mark.test_app_execute_sql_module3 def test_app_execute_sql_module3(): - assert 'app' in dir(app), 'Have you created an instance of the `Flask` class called `app`?' - assert 'execute_sql' in dir(app), 'Have you defined a function named `execute_sql`.' - assert 'open_connection' in get_functions(app.execute_sql), 'Have you called the `open_connection` function in `execute_sql`?' + flask_app = "app" in dir(app) + assert flask_app, "Have you created an instance of the `Flask` class called `app`?" + + execute_sql_function = "execute_sql" in dir(app) + assert execute_sql_function, "Have you defined a function named `execute_sql`?" + + open_call = "open_connection" in get_functions(app.execute_sql) + assert open_call, "Have you called the `open_connection` function in `execute_sql`?" + @pytest.mark.test_app_execute_sql_parameters_module3 def test_app_execute_sql_parameters_module3(): - assert 'execute_sql' in dir(app), 'Have you defined a function named `execute_sql`.' + execute_sql_function = "execute_sql" in dir(app) + assert execute_sql_function, "Have you defined a function named `execute_sql`?" + parameters = inspect.getfullargspec(app.execute_sql) - assert parameters.args[0] == 'sql' and parameters.args[1] == 'values' and parameters.args[2] == 'commit' and parameters.args[3] == 'single', 'Have you added the correct parameters to the `execute_sql` function parameters list?' - assert parameters.defaults[0] == () and parameters.defaults[1] == False and parameters.defaults[2] == False, 'Do the `args` and `one` parameters have the correct defaults in the `execute_sql` function parameters list?' + arg_len = len(parameters.args) == 4 + assert arg_len, "Have you added parameters to the `execute_sql` function?" + + args = ( + parameters.args[0] == "sql" + and parameters.args[1] == "values" + and parameters.args[2] == "commit" + and parameters.args[3] == "single" + ) + assert ( + args + ), "Have you added the correct parameters to the `execute_sql` function parameters list?" + + defaults = ( + parameters.defaults[0] == () + and parameters.defaults[1] == False + and parameters.defaults[2] == False + ) + assert ( + defaults + ), "Do the `args` and `one` parameters have the correct defaults in the `execute_sql` function parameters list?" + @pytest.mark.test_app_execute_sql_execute_module3 def test_app_execute_sql_execute_module3(): - assert 'execute_sql' in dir(app), 'Have you defined a function named `execute_sql`.' - assert 'execute:sql:values' in get_functions(app.execute_sql), 'Have you called the `execute` function in `execute_sql`?' + execute_sql_function = "execute_sql" in dir(app) + assert execute_sql_function, "Have you defined a function named `execute_sql`?" + + execute_call = "execute:sql:values" in get_functions(app.execute_sql) + assert execute_call, "Have you called the `execute` function in `execute_sql`?" + @pytest.mark.test_app_execute_sql_results_module3 def test_app_execute_sql_results_module3(): - assert 'execute_sql' in dir(app), 'Have you defined a function named `execute_sql`.' - assert 'fetchall' in get_functions(app.execute_sql), 'Have you called the `fetchall` function in `execute_sql`?' - assert 'fetchone' in get_functions(app.execute_sql), 'Have you called the `fetchone` function in `execute_sql`?' - assert 'commit' in get_functions(app.execute_sql), 'Have you called the `close` function in `execute_sql`?' - assert 'close' in get_functions(app.execute_sql), 'Have you called the `close` function in `execute_sql`?' + execute_sql_function = "execute_sql" in dir(app) + assert execute_sql_function, "Have you defined a function named `execute_sql`?" + + fetchall = "fetchall" in get_functions(app.execute_sql) + assert fetchall, "Have you called the `fetchall` function in `execute_sql`?" + + fetchone = "fetchone" in get_functions(app.execute_sql) + assert fetchone, "Have you called the `fetchone` function in `execute_sql`?" + + commit = "commit" in get_functions(app.execute_sql) + assert commit, "Have you called the `commit` function in `execute_sql`?" + + close = "close" in get_functions(app.execute_sql) + assert close, "Have you called the `close` function in `execute_sql`?" + + if_statement = len(get_statements(app.execute_sql)) >= 0 + assert if_statement, "Have created an if statement in the `execute_sql` function?" + + results_exists = "results" == get_statements(app.execute_sql)[0]["body/targets/id"] + assert ( + results_exists + ), "Have you assigned the `results` variable to `connection.commit()`?" + with app.app.app_context(): - results = app.execute_sql('SELECT * FROM job', single=True) - assert type(results) != list, 'Have you create an if statement to only return one result in `one` is true?' + results = type(app.execute_sql("SELECT * FROM job", single=True)) != list + assert ( + results + ), "Have you create an if statement to only return one result in `one` is true?" + @pytest.mark.test_app_close_connection_module3 def test_app_close_connection_module3(): - assert 'close_connection' in dir(app), 'Have you defined a function named `close_connection`.' - assert 'getattr:g:_connection:None' in get_functions(app.open_connection), 'Have you used the `getattr` function to get the global `_connection`?' - assert 'close' in get_functions(app.execute_sql), 'Have you called the `close` function in `execute_sql`?' + close_connection = "close_connection" in dir(app) + assert close_connection, "Have you defined a function named `close_connection`?" + + result = [ + item + for item in get_functions(app.open_connection) + if item.startswith("getattr:g:_connection") + ] + result_len = len(result) == 1 + assert ( + result_len + ), "Have you used the `getattr` function to get the global `_connection`?" + + close = "close" in get_functions(app.close_connection) + assert close, "Have you called the `close` function in `close_connection`?" + @pytest.mark.test_app_close_connection_decorator_module3 def test_app_close_connection_decorator_module3(): - assert 'close_connection' in dir(app), 'Have you defined a function named `close_connection`.' - decorator = get_decorators(app.close_connection)['close_connection'][0][0] - assert decorator == 'teardown_appcontext', 'Does `close_connection` have a `teardown_appcontext` decorator?' \ No newline at end of file + close_connection = "close_connection" in dir(app) + assert close_connection, "Have you defined a function named `close_connection`?" + + decorators = get_decorators(app.close_connection)["close_connection"] + + decorators_len = len(decorators) == 1 + assert decorators_len, "Have you added the correct decorator to `close_connection`?" + + decorator = decorators[0][0] + teardown = decorator == "teardown_appcontext" + assert teardown, "Does `close_connection` have a `teardown_appcontext` decorator?" diff --git a/tests/test_module4.py b/tests/test_module4.py index 503d7d5b4..7887d41ca 100644 --- a/tests/test_module4.py +++ b/tests/test_module4.py @@ -4,77 +4,175 @@ from jobs import app from .utils import * + @pytest.mark.test_template_macros_module4 def test_template_macros_module4(): - assert template_exists('_macros'), 'The `_macros.html` template does not exist in the `templates` folder.' + macros_exists = template_exists("_macros") + assert ( + macros_exists + ), "The `_macros.html` template does not exist in the `templates` folder." + @pytest.mark.test_show_job_macro_definition_module4 def test_show_job_macro_definition_module4(): - assert template_exists('_macros'), 'The `_macros.html` template does not exist in the `templates` folder.' - assert 'show_job:job' in template_macros('_macros'), 'Have you created the `show_job` macro and added the correct parameter?' + macros_exists = template_exists("_macros") + assert ( + macros_exists + ), "The `_macros.html` template does not exist in the `templates` folder." + + show_job = "show_job:job" in template_macros("_macros") + assert ( + show_job + ), "Have you created the `show_job` macro and added the correct parameter?" + @pytest.mark.test_show_job_macro_html_module4 def test_show_job_macro_html_module4(): - assert template_exists('_macros'), 'The `_macros.html` template does not exist in the `templates` folder.' - html = template_macro_soup('_macros', 'show_job') - p = html.select('.card .card-header .card-header-title') - div = html.select('.card-content .content') - assert len(p) == 1 and len(div) == 1, 'Has the `HTML` from `templates.html` been copied to the `show_job` macro?' + macros_exists = template_exists("_macros") + assert ( + macros_exists + ), "The `_macros.html` template does not exist in the `templates` folder." + + html = template_macro_soup("_macros", "show_job") + p = html.select(".card .card-header .card-header-title") + div = html.select(".card-content .content") + + copied = len(p) == 1 and len(div) == 1 + assert ( + copied + ), "Has the `HTML` from `templates.html` been copied to the `show_job` macro?" + @pytest.mark.test_show_job_macro_header_module4 def test_show_job_macro_header_module4(): - assert template_exists('_macros'), 'The `_macros.html` template does not exist in the `templates` folder.' - assert 'job:job_id:job:id' in template_functions('_macros', 'url_for'), 'Looks like the job title link `href` is incorrect.' - assert 'job:title' in template_variables('_macros'), 'Looks like the job title link does not have content.' + macros_exists = template_exists("_macros") + assert ( + macros_exists + ), "The `_macros.html` template does not exist in the `templates` folder." + + job_title = "job:title" in template_variables("_macros") + assert job_title, "Looks like the job title link does not have content." + @pytest.mark.test_show_job_macro_body_module4 def test_show_job_macro_body_module4(): - assert template_exists('_macros'), 'The `_macros.html` template does not exist in the `templates` folder.' - assert 'employer:employer_id:job:employer_id' in template_functions('_macros', 'url_for'), 'Looks like the job title link `href` is incorrect.' - assert 'job:employer_name' in template_variables('_macros'), 'Are you showing the employer name?' - assert 'job:salary' in template_variables('_macros'), 'Are you showing the job salary?' - assert 'job:description' in template_variables('_macros'), 'Are you showing the job description?' + macros_exists = template_exists("_macros") + assert ( + macros_exists + ), "The `_macros.html` template does not exist in the `templates` folder." + + employer_name = "job:employer_name" in template_variables("_macros") + assert employer_name, "Are you showing the employer name?" + + salary = "job:salary" in template_variables("_macros") + assert salary, "Are you showing the job salary?" + + description = "job:description" in template_variables("_macros") + assert description, "Are you showing the job description?" + @pytest.mark.test_show_jobs_macro_definition_module4 def test_show_jobs_macro_definition_module4(): - assert template_exists('_macros'), 'The `_macros.html` template does not exist in the `templates` folder.' - assert 'show_jobs:jobs' in template_macros('_macros'), 'Have you created the `show_jobs` macro and added the correct parameter?' + macros_exists = template_exists("_macros") + assert ( + macros_exists + ), "The `_macros.html` template does not exist in the `templates` folder." + + show_jobs = "show_jobs:jobs" in template_macros("_macros") + assert ( + show_jobs + ), "Have you created the `show_jobs` macro and added the correct parameter?" + @pytest.mark.test_show_jobs_macro_for_loop_module4 def test_show_jobs_macro_for_loop_module4(): - assert template_exists('_macros'), 'The `_macros.html` template does not exist in the `templates` folder.' - html = template_macro_soup('_macros', 'show_jobs') - div = html.select('div.columns.is-multiline') - assert len(div) == 1, 'Has a `
` with classes of `columns` and `is-multiline` been added to the `show_jobs` macro?' - assert 'job:jobs' in show_jobs_for(), 'Does the `show_jobs` macro contain a `for` loop?' + macros_exists = template_exists("_macros") + assert ( + macros_exists + ), "The `_macros.html` template does not exist in the `templates` folder." + + show_jobs = "show_jobs:jobs" in template_macros("_macros") + assert ( + show_jobs + ), "Have you created the `show_jobs` macro and added the correct parameter?" + + div = template_macro_soup("_macros", "show_jobs").select("div.columns.is-multiline") + div_len = len(div) == 1 + assert ( + div_len + ), "Has a `
` with classes of `columns` and `is-multiline` been added to the `show_jobs` macro?" + + show_job_for = "job:jobs" in show_jobs_for() + assert show_job_for, "Does the `show_jobs` macro contain a `for` loop?" + @pytest.mark.test_show_jobs_macro_for_loop_body_module4 def test_show_jobs_macro_for_loop_body_module4(): - assert template_exists('_macros'), 'The `_macros.html` template does not exist in the `templates` folder.' - html = template_macro_soup('_macros', 'show_jobs') - div = html.select('div.column.is-half') - assert len(div) == 1, 'Has a `
` with classes of `column` and `is-half` been added to the `show_jobs` macro `for` loop body?' - assert 'show_job:job' in show_jobs_for(), 'Does the `show_jobs` macro call `show_job`?' + macros_exists = template_exists("_macros") + assert ( + macros_exists + ), "The `_macros.html` template does not exist in the `templates` folder." + + show_jobs = "show_jobs:jobs" in template_macros("_macros") + assert ( + show_jobs + ), "Have you created the `show_jobs` macro and added the correct parameter?" + + div = template_macro_soup("_macros", "show_jobs").select("div.column.is-half") + div_len = len(div) == 1 + assert ( + div_len + ), "Has a `
` with classes of `column` and `is-half` been added to the `show_jobs` macro `for` loop body?" + + show_job_call = "show_job:job" in show_jobs_for() + assert show_job_call, "Does the `show_jobs` macro call `show_job`?" + @pytest.mark.test_import_macros_module4 def test_import_macros_module4(): - assert template_exists('_macros'), 'The `_macros.html` template does not exist in the `templates` folder.' - assert '_macros.html:show_job:show_jobs:True' == template_import('layout'), 'Have you imported `_macros.html` in `layouts.html`?' + macros_exists = template_exists("_macros") + assert ( + macros_exists + ), "The `_macros.html` template does not exist in the `templates` folder." + + import_exists = "_macros.html:show_job:show_jobs:True" == template_import("layout") + assert import_exists, "Have you imported `_macros.html` in `layout.html`?" + @pytest.mark.test_index_template_module4 def test_index_template_module4(): - assert template_exists('index'), 'The `index.html` template does not exist in the `templates` folder.' - el = template_data('index').select('.columns .column.is-one-fifth') - assert len(el) == 1, 'Has the `HTML` from `templates.html` been copied to the `index.html` template?' + index_exists = template_exists("index") + assert ( + index_exists + ), "The `index.html` template does not exist in the `templates` folder." + + el = len(template_data("index").select(".columns .column.is-one-fifth")) == 1 + assert ( + el + ), "Has the `HTML` from `templates.html` been copied to the `index.html` template?" + @pytest.mark.test_display_all_jobs_module4 def test_display_all_jobs_module4(): - assert template_exists('index'), 'The `index.html` template does not exist in the `templates` folder.' - assert 'show_jobs:jobs' in template_functions('index', 'show_jobs'), 'Have you call the `show_jobs` macro in the `index.html` file?' + index_exists = template_exists("index") + assert ( + index_exists + ), "The `index.html` template does not exist in the `templates` folder." + + show_jobs_call = "show_jobs:jobs" in template_functions("index", "show_jobs") + assert ( + show_jobs_call + ), "Have you call the `show_jobs` macro in the `index.html` file?" + @pytest.mark.test_app_jobs_route_jobs_module4 def test_app_jobs_route_jobs_module4(): - assert 'jobs' in dir(app), 'Have you created the `jobs` function?' - execute_sql = 'execute_sql:SELECT job.id, job.title, job.description, job.salary, employer.id as employer_id, employer.name as employer_name FROM job JOIN employer ON employer.id = job.employer_id' - assert execute_sql in get_functions(app.jobs), '`execute_sql` has not been called or has the wrong parameters.' - assert 'render_template:index.html:jobs:jobs' in get_functions(app.jobs), 'Have you added `jobs` to the `render_template` call.' + jobs_function = "jobs" in dir(app) + assert jobs_function, "Have you created the `jobs` function?" + + execute_sql = "execute_sql:SELECT job.id, job.title, job.description, job.salary, employer.id as employer_id, employer.name as employer_name FROM job JOIN employer ON employer.id = job.employer_id" + sql_exists = execute_sql in get_functions(app.jobs) + assert sql_exists, "`execute_sql` has not been called or has the wrong parameters." + + new_render_call = "render_template:index.html:jobs:jobs" in get_functions(app.jobs) + assert new_render_call, "Have you added `jobs` to the `render_template` call." diff --git a/tests/test_module5.py b/tests/test_module5.py index 945c033fc..6b3338b06 100644 --- a/tests/test_module5.py +++ b/tests/test_module5.py @@ -4,36 +4,84 @@ from jobs import app from .utils import * + @pytest.mark.test_app_job_template_module5 def test_app_job_template_module5(): - assert template_exists('job'), 'The `job.html` template does not exist in the `templates` folder.' - assert 'layout.html' in template_extends('job'), 'The `job.html` template does not extend `layout.html`.' - assert 'content' in template_block('job'), 'Have you added a template `block` called `content`?' - assert 'show_job:job' in template_functions('job', 'show_job'), 'Have you call the `show_job` macro in the `job.html` file?' + + job_exists = template_exists("job") + assert ( + job_exists + ), "The `job.html` template does not exist in the `templates` folder." + + job_extends = "layout.html" in template_extends("job") + assert job_extends, "The `job.html` template does not extend `layout.html`." + + content_block = "content" in template_block("job") + assert content_block, "Have you added a template `block` called `content`?" + + show_job = "show_job:job" in template_functions("job", "show_job") + assert show_job, "Have you call the `show_job` macro in the `job.html` file?" + @pytest.mark.test_app_job_route_module5 def test_app_job_route_module5(): - assert 'job' in dir(app), 'Have you created the `job` function?' - result = [item for item in get_functions(app.job) if item.startswith('render_template:job.html')] - assert len(result) == 1, 'Have you called the `render_template` function.' + jobs_function = "jobs" in dir(app) + assert jobs_function, "Have you created the `jobs` function?" + + result = [ + item + for item in get_functions(app.job) + if item.startswith("render_template:job.html") + ] + result_len = len(result) == 1 + assert result_len, "Have you called the `render_template` function." + + return_values = get_functions_returns(app.job)[0] + return_exists = ( + return_values["value/args/s"] == "job.html" + and return_values["value/func/id"] == "render_template" + ) + assert return_exists, "Did you return the `render_template` call?" + @pytest.mark.test_app_job_route_decorator_module5 def test_app_job_route_decorator_module5(): - assert 'job' in dir(app), 'Have you created the `job` function?' - assert 'route:/job/' in get_functions(app.job) + jobs_function = "jobs" in dir(app) + assert jobs_function, "Have you created the `jobs` function?" + + job_id = "route:/job/" in get_functions(app.job) + assert job_id, "Have you added a `job_id` parameter to the job function" + @pytest.mark.test_app_job_route_parameter_module5 def test_app_job_route_parameter_module5(): - assert 'job' in dir(app), 'Have you created the `job` function?' - assert 'job_id' in inspect.getfullargspec(app.job).args, 'Have you added the correct parameters to the `job` function parameters list?' + jobs_function = "jobs" in dir(app) + assert jobs_function, "Have you created the `jobs` function?" + + job_href = "job:job_id:job:id" in template_functions("_macros", "url_for") + assert job_href, "Looks like the job title link `href` is incorrect." + + job_id = "job_id" in inspect.getfullargspec(app.job).args + assert ( + job_id + ), "Have you added the correct parameters to the `job` function parameters list?" + @pytest.mark.test_app_job_route_data_module5 def test_app_job_route_data_module5(): - assert 'job' in dir(app), 'Have you created the `job` function?' - execute_sql = 'execute_sql:SELECT job.id, job.title, job.description, job.salary, employer.id as employer_id, employer.name as employer_name FROM job JOIN employer ON employer.id = job.employer_id WHERE job.id = ?:job_id:single:True' - assert execute_sql in get_functions(app.job), '`execute_sql` has not been called or has the wrong parameters.' + jobs_function = "jobs" in dir(app) + assert jobs_function, "Have you created the `jobs` function?" + + execute_sql = "execute_sql:SELECT job.id, job.title, job.description, job.salary, employer.id as employer_id, employer.name as employer_name FROM job JOIN employer ON employer.id = job.employer_id WHERE job.id = ?:job_id:single:True" + result = [item for item in get_functions(app.job) if item.startswith(execute_sql)] + result_len = len(result) == 1 + assert result_len, "`execute_sql` has not been called or has the wrong parameters." + @pytest.mark.test_app_job_route_pass_data_module5 def test_app_job_route_pass_data_module5(): - assert 'job' in dir(app), 'Have you created the `job` function?' - assert 'render_template:job.html:job:job' in get_functions(app.job), 'Have you added `job` to the `render_template` call.' + jobs_function = "jobs" in dir(app) + assert jobs_function, "Have you created the `jobs` function?" + + new_render = "render_template:job.html:job:job" in get_functions(app.job) + assert new_render, "Have you added `job` to the `render_template` call." diff --git a/tests/test_module6.py b/tests/test_module6.py index b680f1fdb..0dd3a5de1 100644 --- a/tests/test_module6.py +++ b/tests/test_module6.py @@ -4,72 +4,216 @@ from jobs import app from .utils import * + @pytest.mark.test_employer_template_module6 def test_employer_template_module6(): - assert template_exists('employer'), 'The `employer.html` template does not exist in the `templates` folder.' - el = template_data('employer').select('.box .media .media-content .content') - assert len(el) == 1, 'Has the `HTML` from `templates.html` been copied to the `employer.html` template?' - assert 'layout.html' in template_extends('employer'), 'The `employer.html` template does not extend `layout.html`.' + employer_exists = template_exists("employer") + assert ( + employer_exists + ), "The `employer.html` template does not exist in the `templates` folder." + + el = ( + len(template_data("employer").select(".box .media .media-content .content")) + == 1 + ) + assert ( + el + ), "Has the `HTML` from `templates.html` been copied to the `employer.html` template?" + + employer_extends = "layout.html" in template_extends("employer") + assert ( + employer_extends + ), "The `employer.html` template does not extend `layout.html`." + @pytest.mark.test_employer_template_details_module6 def test_employer_template_details_module6(): - assert template_exists('employer'), 'The `employer.html` template does not exist in the `templates` folder.' - assert 'employer:name' in template_variables('employer'), "Looks like the `employer['name']` is not present in the template." - assert 'employer:description' in template_variables('employer'), "Looks like the `employer['description']` is not present in the template." + employer_exists = template_exists("employer") + assert ( + employer_exists + ), "The `employer.html` template does not exist in the `templates` folder." + + employer_name = "employer:name" in template_variables("employer") + assert ( + employer_name + ), "Looks like the `employer['name']` is not present in the template." + + employer_description = "employer:description" in template_variables("employer") + assert ( + employer_description + ), "Looks like the `employer['description']` is not present in the template." + @pytest.mark.test_employer_template_all_jobs_module6 def test_employer_template_all_jobs_module6(): - assert template_exists('employer'), 'The `employer.html` template does not exist in the `templates` folder.' - assert 'show_jobs:jobs' in template_functions('employer', 'show_jobs'), 'Have you called the `show_jobs` macro in the `employer.html` file?' + employer_exists = template_exists("employer") + assert ( + employer_exists + ), "The `employer.html` template does not exist in the `templates` folder." + + show_job = "show_jobs:jobs" in template_functions("employer", "show_jobs") + assert ( + show_job + ), "Have you called the `show_jobs` macro in the `employer.html` file?" + @pytest.mark.test_employer_template_reviews_module6 def test_employer_template_reviews_module6(): - assert template_exists('employer'), 'The `employer.html` template does not exist in the `templates` folder.' - assert 'review:reviews' in employer_for(), 'Have you created a `for` loop that cycles through `reviews`?' + employer_exists = template_exists("employer") + assert ( + employer_exists + ), "The `employer.html` template does not exist in the `templates` folder." + + reviews_for = "review:reviews" in employer_for() + assert reviews_for, "Have you created a `for` loop that cycles through `reviews`?" + @pytest.mark.test_employer_template_review_stars_module6 def test_employer_template_review_stars_module6(): - assert template_exists('employer'), 'The `employer.html` template does not exist in the `templates` folder.' - assert '_:range:1:review:rating' in employer_for(), 'Have you created a `for` loop that cycles through `reviews`?' - el = template_data('employer').select('.fa.fa_star.checked') - assert len(el) == 1, 'Has the star `` been added to the `employer.html` template?' + employer_exists = template_exists("employer") + assert ( + employer_exists + ), "The `employer.html` template does not exist in the `templates` folder." + + review_range = "_:range:0:review:rating" in employer_for() + assert review_range, "Have you created a `for` loop that cycles through `reviews`?" + el = len(template_data("employer").select(".fa.fa-star.checked")) == 1 + assert el, "Has the star `` been added to the `employer.html` template?" + @pytest.mark.test_employer_template_review_details_module6 def test_employer_template_review_details_module6(): - assert template_exists('employer'), 'The `employer.html` template does not exist in the `templates` folder.' - assert 'review:title' in template_variables('employer'), "Looks like the `review['title']` is not present in the template." - assert 'review:status' in template_variables('employer'), "Looks like the `review['status']` is not present in the template." - assert 'review:date' in template_variables('employer'), "Looks like the `review['date']` is not present in the template." - assert 'review:review' in template_variables('employer'), "Looks like the `review['review']` is not present in the template." + employer_exists = template_exists("employer") + assert ( + employer_exists + ), "The `employer.html` template does not exist in the `templates` folder." + + review_title = "review:title" in template_variables("employer") + assert ( + review_title + ), "Looks like the `review['title']` is not present in the template." + + review_status = "review:status" in template_variables("employer") + assert ( + review_status + ), "Looks like the `review['status']` is not present in the template." + + review_date = "review:date" in template_variables("employer") + assert ( + review_date + ), "Looks like the `review['date']` is not present in the template." + + review_review = "review:review" in template_variables("employer") + assert ( + review_review + ), "Looks like the `review['review']` is not present in the template." + @pytest.mark.test_app_employer_route_module6 def test_app_employer_route_module6(): - assert 'employer' in dir(app), 'Have you created the `employer` function?' - assert 'route:/employer/' in get_functions(app.employer) - result = [item for item in get_functions(app.employer) if item.startswith('render_template:employer.html')] - assert len(result) == 1, 'Have you called the `render_template` function.' + employer_function = "employer" in dir(app) + assert employer_function, "Have you created the `employer` function?" + + employer_route = "route:/employer/" in get_functions(app.employer) + assert employer_route, "Does `employer` function have the correct route?" + + result = [ + item + for item in get_functions(app.employer) + if item.startswith("render_template:employer.html") + ] + result_len = len(result) == 1 + assert result_len, "Have you called the `render_template` function." + + return_values = get_functions_returns(app.employer)[0] + return_exists = ( + return_values["value/args/s"] == "employer.html" + and return_values["value/func/id"] == "render_template" + ) + assert return_exists, "Did you return the `render_template` call?" + + job_title = "employer:employer_id:job:employer_id" in template_functions( + "_macros", "url_for" + ) + assert ( + job_title + ), "Looks like the job title link `href` is incorrect in `_macros.html." + @pytest.mark.test_app_employer_route_employers_module6 def test_app_employer_route_employers_module6(): - assert 'employer' in dir(app), 'Have you created the `employer` function?' - assert 'employer_id' in inspect.getfullargspec(app.employer).args, 'Have you added the correct parameters to the `employer` function parameter list?' - execute_sql = 'execute_sql:SELECT * FROM employer WHERE id=?:employer_id:single:True' - assert execute_sql in get_functions(app.employer), '`execute_sql` has not been called or has the wrong parameters.' - result = [item for item in get_functions(app.employer) if item.startswith('render_template:employer.html:employer:employer')] - assert len(result) == 1, 'Have you added `employer` to the `render_template` call.' + employer_function = "employer" in dir(app) + assert employer_function, "Have you created the `employer` function?" + + args = "employer_id" in inspect.getfullargspec(app.employer).args + assert ( + args + ), "Have you added the correct parameters to the `employer` function parameter list?" + + execute_sql = ( + "execute_sql:SELECT * FROM employer WHERE id=?:employer_id:single:True" + ) + execute_sql_alternate = ( + "execute_sql:SELECT * FROM employer WHERE id = ?:employer_id:single:True" + ) + + result_sql = [ + item for item in get_functions(app.employer) if item.startswith(execute_sql) + ] + result_sql_len = len(result_sql) == 1 + + result_sql_alternate = [ + item + for item in get_functions(app.employer) + if item.startswith(execute_sql_alternate) + ] + result_sql_alternate_len = len(result_sql_alternate) == 1 + + sql_exists = result_sql_len or result_sql_alternate_len + assert sql_exists, "`execute_sql` has not been called or has the wrong parameters." + + result = [ + item + for item in get_functions(app.employer) + if item.startswith("render_template:employer.html:employer:employer") + ] + result_len = len(result) == 1 + assert result_len, "Have you added `employer` to the `render_template` call." + @pytest.mark.test_app_employer_route_jobs_module6 def test_app_employer_route_jobs_module6(): - assert 'employer' in dir(app), 'Have you created the `employer` function?' - execute_sql = 'execute_sql:SELECT job.id, job.title, job.description, job.salary FROM job JOIN employer ON employer.id = job.employer_id WHERE employer.id = ?:employer_id' - assert execute_sql in get_functions(app.employer), '`execute_sql` has not been called or has the wrong parameters.' - result = [item for item in get_functions(app.employer) if item.startswith('render_template:employer.html:employer:employer:jobs:jobs')] - assert len(result) == 1, 'Have you added `jobs` to the `render_template` call.' + employer_function = "employer" in dir(app) + assert employer_function, "Have you created the `employer` function?" + + execute_sql = "execute_sql:SELECT job.id, job.title, job.description, job.salary FROM job JOIN employer ON employer.id = job.employer_id WHERE employer.id = ?:employer_id" + sql_exists = execute_sql in get_functions(app.employer) + assert sql_exists, "`execute_sql` has not been called or has the wrong parameters." + + result = [ + item + for item in get_functions(app.employer) + if item.startswith("render_template:employer.html:employer:employer:jobs:jobs") + ] + result_len = len(result) == 1 + assert result_len, "Have you added `jobs` to the `render_template` call." + @pytest.mark.test_app_employer_route_reviews_module6 def test_app_employer_route_reviews_module6(): - assert 'employer' in dir(app), 'Have you created the `employer` function?' - execute_sql = 'execute_sql:SELECT review, rating, title, date, status FROM review JOIN employer ON employer.id = review.employer_id WHERE employer.id = ?:employer_id' - assert execute_sql in get_functions(app.employer), '`execute_sql` has not been called or has the wrong parameters.' - result = [item for item in get_functions(app.employer) if item.startswith('render_template:employer.html:employer:employer:jobs:jobs:reviews:reviews')] - assert len(result) == 1, 'Have you added `reviews` to the `render_template` call.' + employer_function = "employer" in dir(app) + assert employer_function, "Have you created the `employer` function?" + + execute_sql = "execute_sql:SELECT review, rating, title, date, status FROM review JOIN employer ON employer.id = review.employer_id WHERE employer.id = ?:employer_id" + sql_exists = execute_sql in get_functions(app.employer) + assert sql_exists, "`execute_sql` has not been called or has the wrong parameters." + + result = [ + item + for item in get_functions(app.employer) + if item.startswith( + "render_template:employer.html:employer:employer:jobs:jobs:reviews:reviews" + ) + ] + result_len = len(result) == 1 + assert result_len, "Have you added `reviews` to the `render_template` call." diff --git a/tests/test_module7.py b/tests/test_module7.py index 2fff3845a..78b77ea75 100644 --- a/tests/test_module7.py +++ b/tests/test_module7.py @@ -5,56 +5,135 @@ from jobs import app from .utils import * + @pytest.mark.test_review_template_module7 def test_review_template_module7(): - assert template_exists('review'), 'The `review.html` template does not exist in the `templates` folder.' - el = template_data('review').select('.field.is-grouped .control .button.is-text') - assert len(el) == 1, 'Has the `HTML` from `templates.html` been copied to the `review.html` template?' - assert 'layout.html' in template_extends('review'), 'The `review.html` template does not extend `layout.html`.' - assert 'employer:employer_id:employer_id' in template_functions('review', 'url_for'), 'Have you called the `url_for` function in the `review.html` file?' + review_exists = template_exists("review") + assert ( + review_exists + ), "The `review.html` template does not exist in the `templates` folder." + + el = template_data("review").select(".field.is-grouped .control .button.is-text") + el_len = len(el) == 1 + assert ( + el_len + ), "Has the `HTML` from `templates.html` been copied to the `review.html` template?" + + review_extends = "layout.html" in template_extends("review") + assert review_extends, "The `review.html` template does not extend `layout.html`." + + review_url_for = "employer:employer_id:employer_id" in template_functions( + "review", "url_for" + ) + assert ( + review_url_for + ), "Have you called the `url_for` function in the `review.html` file?" + @pytest.mark.test_app_review_route_module7 def test_app_review_route_module7(): - assert 'review' in dir(app), 'Have you created the `review` function?' - assert 'employer_id' in inspect.getfullargspec(app.review).args, 'Have you added the correct parameters to the `review` function parameter list?' - assert "route:/employer//review:methods:[{'s': 'GET'}, {'s': 'POST'}]" or "route:/employer//review:methods:[{'s': 'POST'}, {'s': 'GET'}]" in get_functions(app.review), 'Do you have a route decorator with the correct URL pattern and methods?' - result = [item for item in get_functions(app.review) if item.startswith('render_template:review.html:employer_id:employer_id')] - assert len(result) == 1, 'Have you called the `render_template` function with the correct arguments.' + review_function = "review" in dir(app) + assert review_function, "Have you created the `review` function?" + + employer_id = "employer_id" in inspect.getfullargspec(app.review).args + assert ( + employer_id + ), "Have you added the correct parameters to the `review` function parameter list?" + + route_pattern = ( + "route:/employer//review:methods:[{'s': 'GET'}, {'s': 'POST'}]" + or "route:/employer//review:methods:[{'s': 'POST'}, {'s': 'GET'}]" + in get_functions(app.review) + ) + assert ( + route_pattern + ), "Do you have a route decorator with the correct URL pattern and methods?" + + result = [ + item + for item in get_functions(app.review) + if item.startswith("render_template:review.html:employer_id:employer_id") + ] + result_len = len(result) == 1 + assert ( + result_len + ), "Have you called the `render_template` function with the correct arguments." + + return_values = get_functions_returns(app.review) + employer = { + "value/args/args/s": "employer", + "value/args/func/id": "url_for", + "value/args/keywords/arg": "employer_id", + "value/args/keywords/value/id": "employer_id", + "value/func/id": "redirect", + } + render = { + "value/args/s": "review.html", + "value/func/id": "render_template", + "value/keywords/arg": "employer_id", + "value/keywords/value/id": "employer_id", + } + + render_return = render in return_values + assert render_return, "Did you return the `render_template` call?" + + employer_return = employer in return_values + assert employer_return, "Did you return a call to `redirect` and `url_for`?" + @pytest.mark.test_app_review_post_request_check_module7 def test_app_review_post_request_check_module7(): - assert 'review' in dir(app), 'Have you created the `review` function?' - assert 'datetime' in dir(app), '`datetime` has not been imported.' + review_function = "review" in dir(app) + assert review_function, "Have you created the `review` function?" + + datetime_exists = "datetime" in dir(app) + assert datetime_exists, "`datetime` has not been imported." if_statement = get_statements(app.review)[0] - body = if_statement['body'] - post = 'test/comparators/s' in if_statement and 'POST' == if_statement['test/comparators/s'] - method = 'test/left/attr' in if_statement and 'method' == if_statement['test/left/attr'] - request = 'test/left/value/id' in if_statement and 'request' == if_statement['test/left/value/id'] - eq = 'test/ops/node_type' in if_statement and 'Eq' == if_statement['test/ops/node_type'] + body = if_statement["body"] + for item in body: + item.pop("type_comment", None) + item.pop("value/slice/value/value", None) + item.pop("value/args/value", None) + + post = ( + "test/comparators/s" in if_statement + and "POST" == if_statement["test/comparators/s"] + ) + method = ( + "test/left/attr" in if_statement and "method" == if_statement["test/left/attr"] + ) + request = ( + "test/left/value/id" in if_statement + and "request" == if_statement["test/left/value/id"] + ) + eq = ( + "test/ops/node_type" in if_statement + and "Eq" == if_statement["test/ops/node_type"] + ) review = { "targets/id": "review", "value/slice/value/s": "review", "value/value/attr": "form", - "value/value/value/id": "request" + "value/value/value/id": "request", } rating = { "targets/id": "rating", "value/slice/value/s": "rating", "value/value/attr": "form", - "value/value/value/id": "request" + "value/value/value/id": "request", } title = { "targets/id": "title", "value/slice/value/s": "title", "value/value/attr": "form", - "value/value/value/id": "request" + "value/value/value/id": "request", } status = { "targets/id": "status", "value/slice/value/s": "status", "value/value/attr": "form", - "value/value/value/id": "request" + "value/value/value/id": "request", } date = { "targets/id": "date", @@ -62,26 +141,70 @@ def test_app_review_post_request_check_module7(): "value/func/attr": "strftime", "value/func/value/func/attr": "now", "value/func/value/func/value/attr": "datetime", - "value/func/value/func/value/value/id": "datetime" + "value/func/value/func/value/value/id": "datetime", } - assert post and method and request and eq, 'Do you have an `if` statement to test if the request method equals "POST?' - assert review in body, 'Have you created the `review` variable?' - assert rating in body, 'Have you created the `rating` variable?' - assert title in body, 'Have you created the `title` variable?' - assert status in body, 'Have you created the `status` variable?' - assert date in body, 'Have you created the `date` variable?' + + post_if = post and method and request and eq + assert ( + post_if + ), 'Do you have an `if` statement to test if the request method equals "POST?' + + body_review = review in body + assert body_review, "Have you created the `review` variable?" + + body_rating = rating in body + assert body_rating, "Have you created the `rating` variable?" + + body_title = title in body + assert body_title, "Have you created the `title` variable?" + + body_status = status in body + assert body_status, "Have you created the `status` variable?" + + body_date = date in body + assert body_date, "Have you created the `date` variable?" + @pytest.mark.test_app_review_insert_review_module7 def test_app_review_insert_review_module7(): - assert 'review' in dir(app), 'Have you created the `review` function?' + review_function = "review" in dir(app) + assert review_function, "Have you created the `review` function?" execute_sql = "execute_sql:INSERT INTO review (review, rating, title, date, status, employer_id) VALUES (?, ?, ?, ?, ?, ?):[{'id': 'review'}, {'id': 'rating'}, {'id': 'title'}, {'id': 'date'}, {'id': 'status'}, {'id': 'employer_id'}]:commit:True" - assert execute_sql in get_functions(app.review), '`execute_sql` has not been called or has the wrong parameters.' - assert 'redirect' in dir(app), '`redirect` has not been imported from flask.' - assert 'url_for' in dir(app), '`url_for` has not been imported from flask.' - assert 'redirect:employer:url_for:employer_id:employer_id' in get_functions(app.review), 'In the `if` are you redirect back to the employer page?' + result = [ + item for item in get_functions(app.review) if item.startswith(execute_sql) + ] + result_len = len(result) == 1 + assert result_len, "`execute_sql` has not been called or has the wrong parameters." + + +@pytest.mark.test_app_redirect_to_employer_module7 +def test_app_redirect_to_employer_module7(): + review_function = "review" in dir(app) + assert review_function, "Have you created the `review` function?" + + redirect_exists = "redirect" in dir(app) + assert redirect_exists, "`redirect` has not been imported from flask." + + url_for_exists = "url_for" in dir(app) + assert url_for_exists, "`url_for` has not been imported from flask." + + redirect_call = "redirect:employer:url_for:employer_id:employer_id" in get_functions( + app.review + ) or "redirect:employer:employer:url_for:employer_id:employer_id" in get_functions( + app.review + ) + assert redirect_call, "In the `if` are you redirecting back to the employer page?" + @pytest.mark.test_employer_review_button_module7 def test_employer_review_button_module7(): - assert template_exists('employer'), 'The `employer.html` template does not exist in the `templates` folder.' - assert 'review:employer_id:employer:id' in template_functions('employer', 'url_for'), 'In the `if` are you redirect back to the employer page?' + employer_exists = template_exists("employer") + assert ( + employer_exists + ), "The `employer.html` template does not exist in the `templates` folder." + + review_button = "review:employer_id:employer:id" in template_functions( + "employer", "url_for" + ) + assert review_button, "Does the `Create Review` button have the correct `href`?" diff --git a/tests/utils.py b/tests/utils.py index 18398e4a2..cd0b5209b 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -7,9 +7,11 @@ from bs4 import BeautifulSoup from jinja2 import Environment, PackageLoader, exceptions, meta, nodes -env = Environment(loader=PackageLoader('jobs', 'templates')) -def flatten(d, parent_key='', sep='_'): +env = Environment(loader=PackageLoader("jobs", "templates")) + + +def flatten(d, parent_key="", sep="_"): items = [] for k, v in d.items(): new_key = parent_key + sep + k if parent_key else k @@ -19,18 +21,20 @@ def flatten(d, parent_key='', sep='_'): items.append((new_key, v)) return dict(items) + def get_decorators(source): decorators = {} + def visit_FunctionDef(node): decorators[node.name] = [] for n in node.decorator_list: - name = '' + name = "" if isinstance(n, ast.Call): name = n.func.attr if isinstance(n.func, ast.Attribute) else n.func.id else: name = n.attr if isinstance(n, ast.Attribute) else n.id - args = [a.s for a in n.args] if hasattr(n, 'args') else [] + args = [a.s for a in n.args] if hasattr(n, "args") else [] decorators[node.name].append((name, args)) node_iter = ast.NodeVisitor() @@ -38,16 +42,33 @@ def visit_FunctionDef(node): node_iter.visit(ast.parse(inspect.getsource(source))) return decorators + def get_functions(source): functions = [] def visit_Call(node): path = node.func.attr if isinstance(node.func, ast.Attribute) else node.func.id + if len(node.args) != 0: - path += ':' + ':'.join([str(val) for arg in node.args for val in build_dict(arg).values()]) + + args = [] + for arg in node.args: + arg_dict = build_dict(arg) + arg_dict.pop("value", None) + for val in arg_dict.values(): + args.append(str(val)) + + path += ":" + ":".join(args) if len(node.keywords) != 0: - path += ':' + ':'.join([str(val) for keyword in node.keywords for val in build_dict(keyword).values()]) + + path += ":" + ":".join( + [ + str(val) + for keyword in node.keywords + for val in build_dict(keyword).values() + ] + ) functions.append(path) @@ -56,6 +77,22 @@ def visit_Call(node): node_iter.visit(ast.parse(inspect.getsource(source))) return functions + +def get_functions_returns(source): + returns = [] + + def visit_Return(node): + return_dict = build_dict(node) + return_dict.pop("value/args/value", None) + return_dict.pop("value/args/args/value", None) + returns.append(return_dict) + + node_iter = ast.NodeVisitor() + node_iter.visit_Return = visit_Return + node_iter.visit(ast.parse(inspect.getsource(source))) + return returns + + def get_statements(source): statements = [] @@ -67,12 +104,22 @@ def visit_If(node): node_iter.visit(ast.parse(inspect.getsource(source))) return statements + def build_dict(node): result = {} - if node.__class__.__name__ == 'Is' or node.__class__.__name__ == 'Eq': - result['node_type'] = node.__class__.__name__ + if node.__class__.__name__ == "Is" or node.__class__.__name__ == "Eq": + result["node_type"] = node.__class__.__name__ for attr in dir(node): - if not attr.startswith("_") and attr != 'ctx' and attr != 'lineno' and attr != 'col_offset': + if ( + not attr.startswith("_") + and attr != "ctx" + and attr != "lineno" + and attr != "end_lineno" + and attr != "col_offset" + and attr != "end_col_offset" + and attr != "kind" + and attr != "n" + ): value = getattr(node, attr) if isinstance(value, ast.AST): value = build_dict(value) @@ -81,42 +128,57 @@ def build_dict(node): value = final[0] if len(final) == 1 else final if value != []: result[attr] = value - return flatten(result, sep='/') + return flatten(result, sep="/") + def list_routes(app): rules = [] for rule in app.url_map.iter_rules(): - methods = ','.join(sorted(rule.methods)) - if rule.endpoint is not 'static': - rules.append(rule.endpoint + ':' + methods + ':' + str(rule)) + methods = ",".join(sorted(rule.methods)) + if rule.endpoint is not "static": + rules.append(rule.endpoint + ":" + methods + ":" + str(rule)) return rules + def template_values(name, function): values = [] for call in parsed_content(name).find_all(nodes.Call): if call.node.name == function: - values.append(call.args[0].value + ':' + call.kwargs[0].key + ':' + call.kwargs[0].value.value) + values.append( + call.args[0].value + + ":" + + call.kwargs[0].key + + ":" + + call.kwargs[0].value.value + ) return values + def template_functions(name, function_name): functions = [] for call in parsed_content(name).find_all(nodes.Call): if call.node.name == function_name: - args_string = '' - if isinstance(call.node, nodes.Name) and isinstance(call.args[0], nodes.Name): - args_string += call.node.name + ':' + call.args[0].name + args_string = "" + if isinstance(call.node, nodes.Name) and isinstance( + call.args[0], nodes.Name + ): + args_string += call.node.name + ":" + call.args[0].name else: - args = getattr(call, 'args')[0] + args = getattr(call, "args")[0] if isinstance(args, nodes.Const): - args_string += args.value + ':' - kwargs = call.kwargs[0] if len(getattr(call, 'kwargs')) > 0 else getattr(call, 'kwargs') + args_string += args.value + ":" + kwargs = ( + call.kwargs[0] + if len(getattr(call, "kwargs")) > 0 + else getattr(call, "kwargs") + ) if isinstance(kwargs, nodes.Keyword): - args_string += kwargs.key + ':' + args_string += kwargs.key + ":" if isinstance(kwargs.value, nodes.Const): args_string += kwargs.value.value else: @@ -125,88 +187,123 @@ def template_functions(name, function_name): else: args_string += kwargs.value.node.name if isinstance(kwargs.value.arg, nodes.Const): - args_string += ':' + kwargs.value.arg.value + args_string += ":" + kwargs.value.arg.value functions.append(args_string) return functions + def show_jobs_for(): values = [] - for node in parsed_content('_macros').find_all(nodes.For): - values.append(node.target.name + ':' + node.iter.name) + for node in parsed_content("_macros").find_all(nodes.For): + values.append(node.target.name + ":" + node.iter.name) - for call in parsed_content('_macros').find_all(nodes.Call): - if call.node.name == 'show_job' and call.args[0].name == 'job': - values.append('show_job:job') + for call in parsed_content("_macros").find_all(nodes.Call): + if call.node.name == "show_job" and call.args[0].name == "job": + values.append("show_job:job") return values + def employer_for(): values = [] - for node in parsed_content('employer').find_all(nodes.For): + for node in parsed_content("employer").find_all(nodes.For): path = node.target.name if isinstance(node.iter, nodes.Name): - path += ':' + node.iter.name + path += ":" + node.iter.name elif isinstance(node.iter, nodes.Call): - path += ':' + node.iter.node.name + ':' + str(node.iter.args[0].value) + ':' + str(node.iter.args[1].node.name) + ':' + str(node.iter.args[1].arg.value) + path += ( + ":" + + node.iter.node.name + + ":" + + str(node.iter.args[0].value) + + ":" + + str(node.iter.args[1].node.name) + + ":" + + str(node.iter.args[1].arg.value) + ) values.append(path) return values + def template_macros(name): macros = [] for macro in parsed_content(name).find_all(nodes.Macro): - macros.append(macro.name + ':' + macro.args[0].name) + macros.append(macro.name + ":" + macro.args[0].name) return macros + def template_block(name): blocks = [] for block in parsed_content(name).find_all(nodes.Block): blocks.append(block.name) return blocks + def template_macro_soup(name, macro_name): for macro in parsed_content(name).find_all(nodes.Macro): if macro.name == macro_name: - html = '' + html = "" for template_data in macro.find_all(nodes.TemplateData): html += template_data.data return source_soup(html) + def template_data(name): - html = '' + html = "" for node in parsed_content(name).find_all(nodes.TemplateData): html += node.data return source_soup(html) + def template_variables(name): - return [item.node.name + ':' + item.arg.value for item in parsed_content(name).find_all(nodes.Getitem)] + return [ + item.node.name + ":" + item.arg.value + for item in parsed_content(name).find_all(nodes.Getitem) + ] + def template_exists(name): - return os.path.isfile('jobs/templates/' + name + '.html') + return os.path.isfile("jobs/templates/" + name + ".html") + def template_source(name): try: - return env.loader.get_source(env, name + '.html')[0] + return env.loader.get_source(env, name + ".html")[0] except exceptions.TemplateNotFound: return None + def source_soup(source): - return BeautifulSoup(source, 'html.parser') + return BeautifulSoup(source, "html.parser") + def template_soup(name): - return BeautifulSoup(template_source(name), 'html.parser') + return BeautifulSoup(template_source(name), "html.parser") + def template_find(name, tag, limit=None): - return BeautifulSoup(template_source(name), 'html.parser').find_all(tag, limit=limit) + return BeautifulSoup(template_source(name), "html.parser").find_all( + tag, limit=limit + ) + def parsed_content(name): return env.parse(template_source(name)) + def template_extends(name): return list(meta.find_referenced_templates(parsed_content(name))) + def template_import(name): for node in parsed_content(name).find_all(nodes.FromImport): - return node.template.value + ':' + ':'.join(node.names) + ':' + str(node.with_context) \ No newline at end of file + return ( + node.template.value + + ":" + + ":".join(node.names) + + ":" + + str(node.with_context) + )