Improve handling of mocking logic in pytest (#1382)

* WIP

* fix handling of user input (stdin, input, getpass)

* take out redundant pytest step

* fix handling of 'we should' statements

* fix test that doesn't use a config file

* fix another test that uses stdin

Co-authored-by: Micah Jerome Ellison <micah.jerome.ellison@gmail.com>

* remove .tool-versions file per PR feedback

* add comment to clarify why disembodied variables are here

Co-authored-by: Micah Jerome Ellison <micah.jerome.ellison@gmail.com>
This commit is contained in:
Jonathan Wren 2021-12-11 12:35:32 -08:00 committed by GitHub
parent 3518e37087
commit 2ab485de8c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 259 additions and 197 deletions

4
.gitignore vendored
View file

@ -2,7 +2,6 @@
# C extensions # C extensions
*.so *.so
.python-version
# Packages # Packages
*.egg *.egg
@ -17,7 +16,10 @@ sdist
develop-eggs develop-eggs
.installed.cfg .installed.cfg
lib64 lib64
# Versioning
.python-version .python-version
.tool-version
# Installer logs # Installer logs
pip-log.txt pip-log.txt

View file

@ -29,7 +29,7 @@ Feature: Multiple journals
Given the config "multiple.yaml" exists Given the config "multiple.yaml" exists
And we use the config "basic_onefile.yaml" And we use the config "basic_onefile.yaml"
When we run "jrnl --cf multiple.yaml work a long day in the office" When we run "jrnl --cf multiple.yaml work a long day in the office"
Then we should see the message "Entry added to work journal" Then the output should contain "Entry added to work journal"
Scenario: Write to specified journal with a timestamp using an alternate config Scenario: Write to specified journal with a timestamp using an alternate config
Given the config "multiple.yaml" exists Given the config "multiple.yaml" exists
@ -64,7 +64,7 @@ Feature: Multiple journals
Given the config "bug343.yaml" exists Given the config "bug343.yaml" exists
And we use the config "basic_onefile.yaml" And we use the config "basic_onefile.yaml"
When we run "jrnl --cf bug343.yaml a long day in the office" When we run "jrnl --cf bug343.yaml a long day in the office"
Then we should see the message "No default journal configured" Then the output should contain "No default journal configured"
Scenario: Don't crash if no file exists for a configured encrypted journal using an alternate config Scenario: Don't crash if no file exists for a configured encrypted journal using an alternate config
Given the config "multiple.yaml" exists Given the config "multiple.yaml" exists
@ -73,7 +73,7 @@ Feature: Multiple journals
these three eyes these three eyes
these three eyes these three eyes
n n
Then we should see the message "Encrypted journal 'new_encrypted' created" Then the output should contain "Encrypted journal 'new_encrypted' created"
Scenario: Don't overwrite main config when encrypting a journal in an alternate config Scenario: Don't overwrite main config when encrypting a journal in an alternate config
Given the config "basic_onefile.yaml" exists Given the config "basic_onefile.yaml" exists
@ -82,11 +82,14 @@ Feature: Multiple journals
these three eyes these three eyes
these three eyes these three eyes
n n
Then we should see the message "Journal encrypted to features/journals/basic_onefile.journal" Then the output should contain "Journal encrypted to features/journals/basic_onefile.journal"
And the config should contain "encrypt: false" # multiple.yaml remains unchanged And the config should contain "encrypt: false"
Scenario: Don't overwrite main config when decrypting a journal in an alternate config Scenario: Don't overwrite main config when decrypting a journal in an alternate config
Given the config "editor_encrypted.yaml" exists Given the config "editor_encrypted.yaml" exists
And we use the password "bad doggie no biscuit" if prompted
And we use the config "basic_encrypted.yaml" And we use the config "basic_encrypted.yaml"
When we run "jrnl --cf editor_encrypted.yaml --decrypt" When we run "jrnl --cf editor_encrypted.yaml --decrypt"
Then the config should contain "encrypt: true" # basic_encrypted remains unchanged Then the config should contain "encrypt: true"
And the output should not contain "Wrong password"

View file

@ -4,7 +4,7 @@ Feature: Reading and writing to journal with custom date formats
# https://github.com/jrnl-org/jrnl/issues/117 # https://github.com/jrnl-org/jrnl/issues/117
Given we use the config "simple.yaml" Given we use the config "simple.yaml"
When we run "jrnl 2013-11-30 15:42: Project Started." When we run "jrnl 2013-11-30 15:42: Project Started."
Then we should see the message "Entry added" Then the output should contain "Entry added"
When we run "jrnl -999" When we run "jrnl -999"
Then the output should contain "2013-11-30 15:42 Project Started." Then the output should contain "2013-11-30 15:42 Project Started."
@ -13,7 +13,7 @@ Feature: Reading and writing to journal with custom date formats
# https://github.com/jrnl-org/jrnl/issues/185 # https://github.com/jrnl-org/jrnl/issues/185
Given we use the config "simple.yaml" Given we use the config "simple.yaml"
When we run "jrnl 26/06/2099: Planet? Earth. Year? 2099." When we run "jrnl 26/06/2099: Planet? Earth. Year? 2099."
Then we should see the message "Entry added" Then the output should contain "Entry added"
When we run "jrnl -999" When we run "jrnl -999"
Then the output should contain "2099-06-26 09:00 Planet?" Then the output should contain "2099-06-26 09:00 Planet?"
@ -34,7 +34,7 @@ Feature: Reading and writing to journal with custom date formats
Scenario Outline: Writing an entry from command line with custom date Scenario Outline: Writing an entry from command line with custom date
Given we use the config "<config_file>" Given we use the config "<config_file>"
When we run "jrnl <command>" When we run "jrnl <command>"
Then we should see the message "Entry added" Then the output should contain "Entry added"
When we run "jrnl -n 1" When we run "jrnl -n 1"
Then the output should contain "<expected_output>" Then the output should contain "<expected_output>"
@ -87,7 +87,7 @@ Feature: Reading and writing to journal with custom date formats
Given we use the config "simple.yaml" Given we use the config "simple.yaml"
And now is "2019-03-12 01:30:32 PM" And now is "2019-03-12 01:30:32 PM"
When we run "jrnl <command>" When we run "jrnl <command>"
Then we should see the message "Entry added" Then the output should contain "Entry added"
When we run "jrnl -1" When we run "jrnl -1"
Then the output should contain "<expected_output>" Then the output should contain "<expected_output>"
Then the output should contain the date "<date>" Then the output should contain the date "<date>"
@ -109,7 +109,7 @@ Feature: Reading and writing to journal with custom date formats
Given we use the config "simple.yaml" Given we use the config "simple.yaml"
And now is "2019-03-12 01:30:32 PM" And now is "2019-03-12 01:30:32 PM"
When we run "jrnl <command>" When we run "jrnl <command>"
Then we should see the message "Entry added" Then the output should contain "Entry added"
When we run "jrnl -1" When we run "jrnl -1"
Then the output should contain "<expected_output>" Then the output should contain "<expected_output>"
Then the output should contain the date "<date>" Then the output should contain the date "<date>"

View file

@ -2,8 +2,9 @@ Feature: Encrypting and decrypting journals
Scenario: Decrypting a journal Scenario: Decrypting a journal
Given we use the config "encrypted.yaml" Given we use the config "encrypted.yaml"
# And we use the password "bad doggie no biscuit" if prompted
When we run "jrnl --decrypt" and enter "bad doggie no biscuit" When we run "jrnl --decrypt" and enter "bad doggie no biscuit"
Then we should see the message "Journal decrypted" Then the output should contain "Journal decrypted"
And the config for journal "default" should contain "encrypt: false" And the config for journal "default" should contain "encrypt: false"
When we run "jrnl -99 --short" When we run "jrnl -99 --short"
Then the output should be Then the output should be
@ -35,7 +36,7 @@ Feature: Encrypting and decrypting journals
swordfish swordfish
n n
Then we should get no error Then we should get no error
And we should see the message "Journal encrypted" And the output should contain "Journal encrypted"
And the config for journal "default" should contain "encrypt: true" And the config for journal "default" should contain "encrypt: true"
When we run "jrnl -n 1" and enter "swordfish" When we run "jrnl -n 1" and enter "swordfish"
Then we should be prompted for a password Then we should be prompted for a password

View file

@ -3,7 +3,7 @@ Feature: Journals iteracting with the file system in a way that users can see
Scenario: Adding entries to a Folder journal should generate date files Scenario: Adding entries to a Folder journal should generate date files
Given we use the config "empty_folder.yaml" Given we use the config "empty_folder.yaml"
When we run "jrnl 23 July 2013: Testing folder journal." When we run "jrnl 23 July 2013: Testing folder journal."
Then we should see the message "Entry added" Then the output should contain "Entry added"
And the journal directory should contain And the journal directory should contain
2013/07/23.txt 2013/07/23.txt
@ -11,7 +11,7 @@ Feature: Journals iteracting with the file system in a way that users can see
Given we use the config "empty_folder.yaml" Given we use the config "empty_folder.yaml"
When we run "jrnl 23 July 2013: Testing folder journal." When we run "jrnl 23 July 2013: Testing folder journal."
And we run "jrnl 3/7/2014: Second entry of journal." And we run "jrnl 3/7/2014: Second entry of journal."
Then we should see the message "Entry added" Then the output should contain "Entry added"
And the journal directory should contain And the journal directory should contain
2013/07/23.txt 2013/07/23.txt
@ -32,6 +32,7 @@ Feature: Journals iteracting with the file system in a way that users can see
Then the output should contain "This is a new entry in my journal" Then the output should contain "This is a new entry in my journal"
Scenario: Creating journal with relative path should update to absolute path Scenario: Creating journal with relative path should update to absolute path
Given we use no config
When we run "jrnl hello world" and enter When we run "jrnl hello world" and enter
test.txt test.txt
n n

View file

@ -34,7 +34,7 @@ Feature: Multiple journals
Scenario: Tell user which journal was used Scenario: Tell user which journal was used
Given we use the config "multiple.yaml" Given we use the config "multiple.yaml"
When we run "jrnl work a long day in the office" When we run "jrnl work a long day in the office"
Then we should see the message "Entry added to work journal" Then the output should contain "Entry added to work journal"
Scenario: Write to specified journal with a timestamp Scenario: Write to specified journal with a timestamp
Given we use the config "multiple.yaml" Given we use the config "multiple.yaml"

View file

@ -3,9 +3,12 @@ Feature: Implementing Runtime Overrides for Select Configuration Keys
Scenario: Override configured editor with built-in input === editor:'' Scenario: Override configured editor with built-in input === editor:''
Given we use the config "basic_encrypted.yaml" Given we use the config "basic_encrypted.yaml"
And we use the password "test" if prompted And we use the password "test" if prompted
When we run "jrnl --config-override editor ''" When we run "jrnl --config-override editor ''" and enter
This is a journal entry
Then the stdin prompt should have been called Then the stdin prompt should have been called
And the editor should not have been called And the editor should not have been called
When we run "jrnl -1"
Then the output should contain "This is a journal entry"
Scenario: Postconfig commands with overrides Scenario: Postconfig commands with overrides
@ -61,7 +64,7 @@ Feature: Implementing Runtime Overrides for Select Configuration Keys
And we use the password "test" if prompted And we use the password "test" if prompted
When we run "jrnl --config-override journals.default features/journals/simple.journal 20 Mar 2000: The rain in Spain comes from clouds" When we run "jrnl --config-override journals.default features/journals/simple.journal 20 Mar 2000: The rain in Spain comes from clouds"
Then we should get no error Then we should get no error
And we should see the message "Entry added" And the output should contain "Entry added"
When we run "jrnl -3 --config-override journals.default features/journals/simple.journal" When we run "jrnl -3 --config-override journals.default features/journals/simple.journal"
Then the output should be Then the output should be
2000-03-20 09:00 The rain in Spain comes from clouds 2000-03-20 09:00 The rain in Spain comes from clouds
@ -78,7 +81,7 @@ Feature: Implementing Runtime Overrides for Select Configuration Keys
And we use the password "test" if prompted And we use the password "test" if prompted
When we run "jrnl --config-override journals.temp features/journals/simple.journal temp Sep 06 1969: @say Ni" When we run "jrnl --config-override journals.temp features/journals/simple.journal temp Sep 06 1969: @say Ni"
Then we should get no error Then we should get no error
And we should see the message "Entry added" And the output should contain "Entry added"
When we run "jrnl --config-override journals.temp features/journals/simple.journal temp -3" When we run "jrnl --config-override journals.temp features/journals/simple.journal temp -3"
Then the output should be Then the output should be
1969-09-06 09:00 @say Ni 1969-09-06 09:00 @say Ni

View file

@ -55,7 +55,7 @@ Feature: Using the installed keyring
this password will not be saved in keyring this password will not be saved in keyring
this password will not be saved in keyring this password will not be saved in keyring
y y
Then we should see the message "Failed to retrieve keyring" Then the output should contain "Failed to retrieve keyring"
And we should get no error And we should get no error
And we should be prompted for a password And we should be prompted for a password
And the config for journal "default" should contain "encrypt: true" And the config for journal "default" should contain "encrypt: true"
@ -69,7 +69,7 @@ Feature: Using the installed keyring
Then the error output should contain "Failed to retrieve keyring" Then the error output should contain "Failed to retrieve keyring"
And we should get no error And we should get no error
And we should be prompted for a password And we should be prompted for a password
And we should see the message "Journal decrypted" And the output should contain "Journal decrypted"
And the config for journal "default" should contain "encrypt: false" And the config for journal "default" should contain "encrypt: false"
When we run "jrnl --short" When we run "jrnl --short"
Then we should not be prompted for a password Then we should not be prompted for a password
@ -96,7 +96,7 @@ Feature: Using the installed keyring
swordfish swordfish
sordfish sordfish
Then we should be prompted for a password Then we should be prompted for a password
And we should see the message "Passwords did not match" And the output should contain "Passwords did not match"
And the config for journal "default" should not contain "encrypt: true" And the config for journal "default" should not contain "encrypt: true"
When we run "jrnl --short" When we run "jrnl --short"
Then the output should be Then the output should be
@ -113,8 +113,8 @@ Feature: Using the installed keyring
swordfish swordfish
n n
Then we should be prompted for a password Then we should be prompted for a password
And we should see the message "Passwords did not match" And the output should contain "Passwords did not match"
And we should see the message "Journal encrypted" And the output should contain "Journal encrypted"
And the config for journal "default" should contain "encrypt: true" And the config for journal "default" should contain "encrypt: true"
When we run "jrnl -1" and enter "swordfish" When we run "jrnl -1" and enter "swordfish"
Then we should be prompted for a password Then we should be prompted for a password

View file

@ -3,7 +3,7 @@ Feature: Searching in a journal
Scenario Outline: Displaying entries using -on today should display entries created today Scenario Outline: Displaying entries using -on today should display entries created today
Given we use the config "<config_file>" Given we use the config "<config_file>"
When we run "jrnl today: Adding an entry right now." When we run "jrnl today: Adding an entry right now."
Then we should see the message "Entry added" Then the output should contain "Entry added"
When we run "jrnl -on today" When we run "jrnl -on today"
Then the output should contain "Adding an entry right now." Then the output should contain "Adding an entry right now."
But the output should not contain "Everything is alright" But the output should not contain "Everything is alright"
@ -18,11 +18,11 @@ Feature: Searching in a journal
Scenario Outline: Displaying entries using -from day should display correct entries Scenario Outline: Displaying entries using -from day should display correct entries
Given we use the config "<config_file>" Given we use the config "<config_file>"
When we run "jrnl yesterday: This thing happened yesterday" When we run "jrnl yesterday: This thing happened yesterday"
Then we should see the message "Entry added" Then the output should contain "Entry added"
When we run "jrnl today at 11:59pm: Adding an entry right now." When we run "jrnl today at 11:59pm: Adding an entry right now."
Then we should see the message "Entry added" Then the output should contain "Entry added"
When we run "jrnl tomorrow: A future entry." When we run "jrnl tomorrow: A future entry."
Then we should see the message "Entry added" Then the output should contain "Entry added"
When we run "jrnl -from today" When we run "jrnl -from today"
Then the output should contain "Adding an entry right now." Then the output should contain "Adding an entry right now."
And the output should contain "A future entry." And the output should contain "A future entry."
@ -37,11 +37,11 @@ Feature: Searching in a journal
Scenario Outline: Displaying entries using -from and -to day should display correct entries Scenario Outline: Displaying entries using -from and -to day should display correct entries
Given we use the config "<config_file>" Given we use the config "<config_file>"
When we run "jrnl yesterday: This thing happened yesterday" When we run "jrnl yesterday: This thing happened yesterday"
Then we should see the message "Entry added" Then the output should contain "Entry added"
When we run "jrnl today at 11:59pm: Adding an entry right now." When we run "jrnl today at 11:59pm: Adding an entry right now."
Then we should see the message "Entry added" Then the output should contain "Entry added"
When we run "jrnl tomorrow: A future entry." When we run "jrnl tomorrow: A future entry."
Then we should see the message "Entry added" Then the output should contain "Entry added"
When we run "jrnl -from yesterday -to today" When we run "jrnl -from yesterday -to today"
Then the output should contain "This thing happened yesterday" Then the output should contain "This thing happened yesterday"
And the output should contain "Adding an entry right now." And the output should contain "Adding an entry right now."
@ -118,9 +118,9 @@ Feature: Searching in a journal
Scenario: Out of order entries to a Folder journal should be listed in date order Scenario: Out of order entries to a Folder journal should be listed in date order
Given we use the config "empty_folder.yaml" Given we use the config "empty_folder.yaml"
When we run "jrnl 3/7/2014 4:37pm: Second entry of journal." When we run "jrnl 3/7/2014 4:37pm: Second entry of journal."
Then we should see the message "Entry added" Then the output should contain "Entry added"
When we run "jrnl 23 July 2013: Testing folder journal." When we run "jrnl 23 July 2013: Testing folder journal."
Then we should see the message "Entry added" Then the output should contain "Entry added"
When we run "jrnl -2" When we run "jrnl -2"
Then the output should be Then the output should be
2013-07-23 09:00 Testing folder journal. 2013-07-23 09:00 Testing folder journal.

View file

@ -3,7 +3,7 @@ Feature: Starring entries
Scenario Outline: Starring an entry will mark it in the journal file Scenario Outline: Starring an entry will mark it in the journal file
Given we use the config "<config_file>" Given we use the config "<config_file>"
When we run "jrnl 20 july 2013 *: Best day of my life!" When we run "jrnl 20 july 2013 *: Best day of my life!"
Then we should see the message "Entry added" Then the output should contain "Entry added"
When we run "jrnl -on 2013-07-20 -starred" When we run "jrnl -on 2013-07-20 -starred"
Then the output should contain "2013-07-20 09:00 Best day of my life!" Then the output should contain "2013-07-20 09:00 Best day of my life!"
@ -30,6 +30,6 @@ Feature: Starring entries
Scenario: Starring an entry will mark it in an encrypted journal Scenario: Starring an entry will mark it in an encrypted journal
Given we use the config "encrypted.yaml" Given we use the config "encrypted.yaml"
When we run "jrnl 20 july 2013 *: Best day of my life!" and enter "bad doggie no biscuit" When we run "jrnl 20 july 2013 *: Best day of my life!" and enter "bad doggie no biscuit"
Then we should see the message "Entry added" Then the output should contain "Entry added"
When we run "jrnl -on 2013-07-20 -starred" and enter "bad doggie no biscuit" When we run "jrnl -on 2013-07-20 -starred" and enter "bad doggie no biscuit"
Then the output should contain "2013-07-20 09:00 Best day of my life!" Then the output should contain "2013-07-20 09:00 Best day of my life!"

View file

@ -46,7 +46,7 @@ Feature: Writing new entries.
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "bad doggie no biscuit" if prompted And we use the password "bad doggie no biscuit" if prompted
When we run "jrnl 23 july 2013: A cold and stormy day. I ate crisps on the sofa." When we run "jrnl 23 july 2013: A cold and stormy day. I ate crisps on the sofa."
Then we should see the message "Entry added" Then the output should contain "Entry added"
When we run "jrnl -n 1" When we run "jrnl -n 1"
Then the output should contain "2013-07-23 09:00 A cold and stormy day." Then the output should contain "2013-07-23 09:00 A cold and stormy day."
@ -61,7 +61,7 @@ Feature: Writing new entries.
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "test" if prompted And we use the password "test" if prompted
When we run "jrnl this is a partial --edit" When we run "jrnl this is a partial --edit"
Then we should see the message "Entry added" Then the output should contain "Entry added"
Then the editor should have been called Then the editor should have been called
And the editor file content should be And the editor file content should be
this is a partial this is a partial
@ -110,7 +110,7 @@ Feature: Writing new entries.
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "bad doggie no biscuit" if prompted And we use the password "bad doggie no biscuit" if prompted
When we run "jrnl 23 july 2013: A cold and stormy day. I ate crisps on the sofa." When we run "jrnl 23 july 2013: A cold and stormy day. I ate crisps on the sofa."
Then we should see the message "Entry added" Then the output should contain "Entry added"
When we run "jrnl -n 1" When we run "jrnl -n 1"
Then the output should not contain "Life is good" Then the output should not contain "Life is good"
@ -125,7 +125,7 @@ Feature: Writing new entries.
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "bad doggie no biscuit" if prompted And we use the password "bad doggie no biscuit" if prompted
When we run "jrnl 04-24-2014: Created a new website - empty.com. Hope to get a lot of traffic." When we run "jrnl 04-24-2014: Created a new website - empty.com. Hope to get a lot of traffic."
Then we should see the message "Entry added" Then the output should contain "Entry added"
When we run "jrnl -1" When we run "jrnl -1"
Then the output should be Then the output should be
2014-04-24 09:00 Created a new website - empty.com. 2014-04-24 09:00 Created a new website - empty.com.
@ -142,7 +142,7 @@ Feature: Writing new entries.
Given we use the config "<config_file>" Given we use the config "<config_file>"
And we use the password "bad doggie no biscuit" if prompted And we use the password "bad doggie no biscuit" if prompted
When we run "jrnl 23 july 2013: 🌞 sunny day. Saw an 🐘" When we run "jrnl 23 july 2013: 🌞 sunny day. Saw an 🐘"
Then we should see the message "Entry added" Then the output should contain "Entry added"
When we run "jrnl -n 1" When we run "jrnl -n 1"
Then the output should contain "🌞" Then the output should contain "🌞"
And the output should contain "🐘" And the output should contain "🐘"
@ -199,7 +199,7 @@ Feature: Writing new entries.
Scenario: Title with an embedded period on DayOne journal Scenario: Title with an embedded period on DayOne journal
Given we use the config "dayone.yaml" Given we use the config "dayone.yaml"
When we run "jrnl 04-24-2014: Ran 6.2 miles today in 1:02:03. I am feeling sore because I forgot to stretch." When we run "jrnl 04-24-2014: Ran 6.2 miles today in 1:02:03. I am feeling sore because I forgot to stretch."
Then we should see the message "Entry added" Then the output should contain "Entry added"
When we run "jrnl -1" When we run "jrnl -1"
Then the output should be Then the output should be
2014-04-24 09:00 Ran 6.2 miles today in 1:02:03. 2014-04-24 09:00 Ran 6.2 miles today in 1:02:03.
@ -208,7 +208,7 @@ Feature: Writing new entries.
Scenario: Opening an folder that's not a DayOne folder should treat as folder journal Scenario: Opening an folder that's not a DayOne folder should treat as folder journal
Given we use the config "empty_folder.yaml" Given we use the config "empty_folder.yaml"
When we run "jrnl 23 july 2013: Testing folder journal." When we run "jrnl 23 july 2013: Testing folder journal."
Then we should see the message "Entry added" Then the output should contain "Entry added"
When we run "jrnl -1" When we run "jrnl -1"
Then the output should be "2013-07-23 09:00 Testing folder journal." Then the output should be "2013-07-23 09:00 Testing folder journal."

View file

@ -8,17 +8,19 @@ import tempfile
from keyring import backend from keyring import backend
from keyring import errors from keyring import errors
from keyring import set_keyring
from pytest import fixture from pytest import fixture
from unittest.mock import patch
from .helpers import get_fixture
import toml import toml
from jrnl.config import load_config from jrnl.config import load_config
from jrnl.os_compat import split_args
# --- Keyring --- # # --- Keyring --- #
@fixture @fixture
def keyring(): def keyring():
set_keyring(NoKeyring()) return NoKeyring()
@fixture @fixture
@ -75,13 +77,90 @@ class FailedKeyring(backend.KeyringBackend):
# ----- Misc ----- # # ----- Misc ----- #
@fixture @fixture
def cli_run(): def cli_run(
return {"status": 0, "stdout": None, "stderr": None} mock_factories,
mock_args,
mock_is_tty,
mock_config_path,
mock_editor,
mock_user_input,
mock_overrides,
mock_password,
):
# Check if we need more mocks
mock_factories.update(mock_args)
mock_factories.update(mock_is_tty)
mock_factories.update(mock_overrides)
mock_factories.update(mock_editor)
mock_factories.update(mock_config_path)
mock_factories.update(mock_user_input)
mock_factories.update(mock_password)
return {
"status": 0,
"stdout": None,
"stderr": None,
"mocks": {},
"mock_factories": mock_factories,
}
@fixture @fixture
def mocks(): def mock_factories():
return dict() return {}
@fixture
def mock_args(cache_dir, request):
def _mock_args():
command = get_fixture(request, "command", "")
if cache_dir["exists"]:
command = command.format(cache_dir=cache_dir["path"])
args = split_args(command)
return patch("sys.argv", ["jrnl"] + args)
return {"args": _mock_args}
@fixture
def mock_is_tty(is_tty):
return {"is_tty": lambda: patch("sys.stdin.isatty", return_value=is_tty)}
@fixture
def mock_overrides(config_in_memory):
from jrnl.override import apply_overrides
def my_overrides(*args, **kwargs):
result = apply_overrides(*args, **kwargs)
config_in_memory["overrides"] = result
return result
return {
"overrides": lambda: patch(
"jrnl.jrnl.apply_overrides", side_effect=my_overrides
)
}
@fixture
def mock_config_path(request):
config_path = get_fixture(request, "config_path")
if not config_path:
return {}
return {
"config_path_install": lambda: patch(
"jrnl.install.get_config_path", return_value=config_path
),
"config_path_config": lambda: patch(
"jrnl.config.get_config_path", return_value=config_path
),
}
@fixture @fixture
@ -94,12 +173,6 @@ def working_dir(request):
return os.path.join(request.config.rootpath, "tests") return os.path.join(request.config.rootpath, "tests")
@fixture
def config_path(temp_dir):
os.chdir(temp_dir.name)
return temp_dir.name + "/jrnl.yaml"
@fixture @fixture
def toml_version(working_dir): def toml_version(working_dir):
pyproject = os.path.join(working_dir, "..", "pyproject.toml") pyproject = os.path.join(working_dir, "..", "pyproject.toml")
@ -108,8 +181,23 @@ def toml_version(working_dir):
@fixture @fixture
def password(): def mock_password(request):
return "" def _mock_password():
password = get_fixture(request, "password")
user_input = get_fixture(request, "user_input")
if password:
password = password.splitlines()
elif user_input:
password = user_input.splitlines()
if not password:
password = Exception("Unexpected call for password")
return patch("getpass.getpass", side_effect=password)
return {"getpass": _mock_password}
@fixture @fixture
@ -127,19 +215,36 @@ def str_value():
return "" return ""
@fixture
def command():
return ""
@fixture @fixture
def should_not(): def should_not():
return False return False
@fixture @fixture
def user_input(): def mock_user_input(request, is_tty):
return "" def _generator(target):
def _mock_user_input():
user_input = get_fixture(request, "user_input", None)
if user_input is None:
user_input = Exception("Unexpected call for user input")
else:
user_input = user_input.splitlines() if is_tty else [user_input]
return patch(target, side_effect=user_input)
return _mock_user_input
return {
"stdin": _generator("sys.stdin.read"),
"input": _generator("builtins.input"),
}
@fixture
def is_tty(input_method):
assert input_method in ["", "enter", "pipe"]
return input_method != "pipe"
@fixture @fixture
@ -187,7 +292,7 @@ def editor_state():
@fixture @fixture
def editor(editor_state): def mock_editor(editor_state):
def _mock_editor(editor_command): def _mock_editor(editor_command):
tmpfile = editor_command[-1] tmpfile = editor_command[-1]
@ -203,4 +308,4 @@ def editor(editor_state):
file_content = f.read() file_content = f.read()
editor_state["tmpfile"]["content"] = file_content editor_state["tmpfile"]["content"] = file_content
return _mock_editor return {"editor": lambda: patch("subprocess.call", side_effect=_mock_editor)}

View file

@ -11,7 +11,6 @@ from unittest.mock import MagicMock
from unittest.mock import patch from unittest.mock import patch
from xml.etree import ElementTree from xml.etree import ElementTree
from keyring import set_keyring
from pytest_bdd import given from pytest_bdd import given
from pytest_bdd.parsers import parse from pytest_bdd.parsers import parse
@ -20,6 +19,7 @@ from jrnl.time import __get_pdt_calendar
from .fixtures import FailedKeyring from .fixtures import FailedKeyring
from .fixtures import TestKeyring from .fixtures import TestKeyring
from .helpers import get_fixture
@given(parse("we {editor_method} to the editor if opened\n{editor_input}")) @given(parse("we {editor_method} to the editor if opened\n{editor_input}"))
@ -36,9 +36,8 @@ def we_enter_editor(editor_method, editor_input, editor_state):
editor_state["intent"] = {"method": file_method, "input": editor_input} editor_state["intent"] = {"method": file_method, "input": editor_input}
@given(parse('now is "<date_str>"'))
@given(parse('now is "{date_str}"')) @given(parse('now is "{date_str}"'))
def now_is_str(date_str, mocks): def now_is_str(date_str, mock_factories):
class DatetimeMagicMock(MagicMock): class DatetimeMagicMock(MagicMock):
# needed because jrnl does some reflection on datetime # needed because jrnl does some reflection on datetime
def __instancecheck__(self, subclass): def __instancecheck__(self, subclass):
@ -63,8 +62,8 @@ def now_is_str(date_str, mocks):
date_str_input, mocked_now() date_str_input, mocked_now()
) )
mocks["datetime"] = patch("datetime.datetime", new=datetime_mock) mock_factories["datetime"] = lambda: patch("datetime.datetime", new=datetime_mock)
mocks["calendar_parse"] = patch( mock_factories["calendar_parse"] = lambda: patch(
"jrnl.time.__get_pdt_calendar", return_value=calendar_mock "jrnl.time.__get_pdt_calendar", return_value=calendar_mock
) )
@ -73,17 +72,22 @@ def now_is_str(date_str, mocks):
@given(parse("we have a {keyring_type} keyring"), target_fixture="keyring") @given(parse("we have a {keyring_type} keyring"), target_fixture="keyring")
def we_have_type_of_keyring(keyring_type): def we_have_type_of_keyring(keyring_type):
if keyring_type == "failed": if keyring_type == "failed":
set_keyring(FailedKeyring()) return FailedKeyring()
else: else:
set_keyring(TestKeyring()) return TestKeyring()
@given(parse('we use the config "{config_file}"'), target_fixture="config_path") @given(parse('we use the config "{config_file}"'), target_fixture="config_path")
@given('we use the config "<config_file>"', target_fixture="config_path") @given(parse("we use no config"), target_fixture="config_path")
def we_use_the_config(config_file, temp_dir, working_dir): def we_use_the_config(request, temp_dir, working_dir):
config_file = get_fixture(request, "config_file")
# Move into temp dir as cwd # Move into temp dir as cwd
os.chdir(temp_dir.name) os.chdir(temp_dir.name)
if not config_file:
return os.path.join(temp_dir.name, "non_existing_config.yaml")
# Copy the config file over # Copy the config file over
config_source = os.path.join(working_dir, "data", "configs", config_file) config_source = os.path.join(working_dir, "data", "configs", config_file)
config_dest = os.path.join(temp_dir.name, config_file) config_dest = os.path.join(temp_dir.name, config_file)
@ -106,7 +110,6 @@ def we_use_the_config(config_file, temp_dir, working_dir):
@given(parse('the config "{config_file}" exists'), target_fixture="config_path") @given(parse('the config "{config_file}" exists'), target_fixture="config_path")
@given('the config "<config_file>" exists', target_fixture="config_path")
def config_exists(config_file, temp_dir, working_dir): def config_exists(config_file, temp_dir, working_dir):
config_source = os.path.join(working_dir, "data", "configs", config_file) config_source = os.path.join(working_dir, "data", "configs", config_file)
config_dest = os.path.join(temp_dir.name, config_file) config_dest = os.path.join(temp_dir.name, config_file)

View file

@ -49,3 +49,24 @@ def get_nested_val(dictionary, path, *default):
if default: if default:
return default[0] return default[0]
raise raise
# @see: https://stackoverflow.com/a/41599695/569146
def spy_wrapper(wrapped_function):
from unittest import mock
mock = mock.MagicMock()
def wrapper(self, *args, **kwargs):
mock(*args, **kwargs)
return wrapped_function(self, *args, **kwargs)
wrapper.mock = mock
return wrapper
def get_fixture(request, name, default=None):
result = default
if name in request.node.fixturenames:
result = request.getfixturevalue(name)
return result

View file

@ -30,38 +30,47 @@ def output_should_match(regex, cli_run):
assert matches, f"\nRegex didn't match:\n{regex}\n{str(out)}\n{str(matches)}" assert matches, f"\nRegex didn't match:\n{regex}\n{str(out)}\n{str(matches)}"
@then(parse("the output should contain\n{expected_output}")) @then(parse("the output {should_or_should_not} contain\n{expected_output}"))
@then(parse('the output should contain "{expected_output}"')) @then(parse('the output {should_or_should_not} contain "{expected_output}"'))
@then('the output should contain "<expected_output>"') @then(
@then(parse("the {which_output_stream} output should contain\n{expected_output}")) parse(
@then(parse('the {which_output_stream} output should contain "{expected_output}"')) "the {which_output_stream} output {should_or_should_not} contain\n{expected_output}"
def output_should_contain(expected_output, which_output_stream, cli_run): )
)
@then(
parse(
'the {which_output_stream} output {should_or_should_not} contain "{expected_output}"'
)
)
def output_should_contain(
expected_output, which_output_stream, cli_run, should_or_should_not
):
we_should = parse_should_or_should_not(should_or_should_not)
assert expected_output assert expected_output
if which_output_stream is None: if which_output_stream is None:
assert (expected_output in cli_run["stdout"]) or ( assert ((expected_output in cli_run["stdout"]) == we_should) or (
expected_output in cli_run["stderr"] (expected_output in cli_run["stderr"]) == we_should
) )
elif which_output_stream == "standard": elif which_output_stream == "standard":
assert expected_output in cli_run["stdout"] assert (expected_output in cli_run["stdout"]) == we_should
elif which_output_stream == "error": elif which_output_stream == "error":
assert expected_output in cli_run["stderr"] assert (expected_output in cli_run["stderr"]) == we_should
else: else:
assert expected_output in cli_run[which_output_stream] assert (expected_output in cli_run[which_output_stream]) == we_should
@then(parse("the output should not contain\n{expected_output}")) @then(parse("the output should not contain\n{expected_output}"))
@then(parse('the output should not contain "{expected_output}"')) @then(parse('the output should not contain "{expected_output}"'))
@then('the output should not contain "<expected_output>"')
def output_should_not_contain(expected_output, cli_run): def output_should_not_contain(expected_output, cli_run):
assert expected_output not in cli_run["stdout"] assert expected_output not in cli_run["stdout"]
@then(parse("the output should be\n{expected_output}")) @then(parse("the output should be\n{expected_output}"))
@then(parse('the output should be "{expected_output}"')) @then(parse('the output should be "{expected_output}"'))
@then('the output should be "<expected_output>"')
def output_should_be(expected_output, cli_run): def output_should_be(expected_output, cli_run):
actual = cli_run["stdout"].strip() actual = cli_run["stdout"].strip()
expected = expected_output.strip() expected = expected_output.strip()
@ -75,7 +84,6 @@ def output_should_be_empty(cli_run):
@then(parse('the output should contain the date "{date}"')) @then(parse('the output should contain the date "{date}"'))
@then('the output should contain the date "<date>"')
def output_should_contain_date(date, cli_run): def output_should_contain_date(date, cli_run):
assert date and date in cli_run["stdout"] assert date and date in cli_run["stdout"]
@ -94,12 +102,6 @@ def output_should_be_columns_wide(cli_run, width):
assert len(line) <= width assert len(line) <= width
@then(parse('we should see the message "{text}"'))
def should_see_the_message(text, cli_run):
out = cli_run["stderr"]
assert text in out, [text, out]
@then( @then(
parse( parse(
'the config for journal "{journal_name}" {should_or_should_not} contain "{some_yaml}"' 'the config for journal "{journal_name}" {should_or_should_not} contain "{some_yaml}"'
@ -126,10 +128,7 @@ def config_var_on_disk(config_on_disk, journal_name, should_or_should_not, some_
# `expected` objects formatted in yaml only compare one level deep # `expected` objects formatted in yaml only compare one level deep
actual_slice = {key: actual.get(key, None) for key in expected.keys()} actual_slice = {key: actual.get(key, None) for key in expected.keys()}
if we_should: assert (expected == actual_slice) == we_should
assert expected == actual_slice
else:
assert expected != actual_slice
@then( @then(
@ -160,10 +159,7 @@ def config_var_in_memory(
# `expected` objects formatted in yaml only compare one level deep # `expected` objects formatted in yaml only compare one level deep
actual_slice = {key: get_nested_val(actual, key) for key in expected.keys()} actual_slice = {key: get_nested_val(actual, key) for key in expected.keys()}
if we_should: assert (expected == actual_slice) == we_should
assert expected == actual_slice
else:
assert expected != actual_slice
@then("we should be prompted for a password") @then("we should be prompted for a password")
@ -355,10 +351,7 @@ def count_elements(number, item, cli_run):
def count_editor_args(num_args, cli_run, editor_state, should_or_should_not): def count_editor_args(num_args, cli_run, editor_state, should_or_should_not):
we_should = parse_should_or_should_not(should_or_should_not) we_should = parse_should_or_should_not(should_or_should_not)
if we_should: assert cli_run["mocks"]["editor"].called == we_should
assert cli_run["mocks"]["editor"].called
else:
assert not cli_run["mocks"]["editor"].called
if isinstance(num_args, int): if isinstance(num_args, int):
assert len(editor_state["command"]) == int(num_args) assert len(editor_state["command"]) == int(num_args)
@ -368,10 +361,7 @@ def count_editor_args(num_args, cli_run, editor_state, should_or_should_not):
def stdin_prompt_called(cli_run, should_or_should_not): def stdin_prompt_called(cli_run, should_or_should_not):
we_should = parse_should_or_should_not(should_or_should_not) we_should = parse_should_or_should_not(should_or_should_not)
if we_should: assert cli_run["mocks"]["stdin"].called == we_should
assert cli_run["mocks"]["stdin"].called
else:
assert not cli_run["mocks"]["stdin"].called
@then(parse('the editor filename should end with "{suffix}"')) @then(parse('the editor filename should end with "{suffix}"'))

View file

@ -3,14 +3,12 @@
from contextlib import ExitStack from contextlib import ExitStack
import os import os
from unittest.mock import patch
from pytest_bdd import parsers
from pytest_bdd import when from pytest_bdd import when
from pytest_bdd.parsers import parse from pytest_bdd.parsers import parse
from pytest_bdd.parsers import re
from jrnl.cli import cli from jrnl.cli import cli
from jrnl.os_compat import split_args
@when(parse('we change directory to "{directory_name}"')) @when(parse('we change directory to "{directory_name}"'))
@ -21,103 +19,38 @@ def when_we_change_directory(directory_name):
os.chdir(directory_name) os.chdir(directory_name)
# These variables are used in the `@when(re(...))` section below
command = '(?P<command>[^"]+)'
input_method = "(?P<input_method>enter|pipe)"
user_input = '(?P<user_input>[^"]+)'
@when(parse('we run "jrnl {command}" and {input_method}\n{user_input}')) @when(parse('we run "jrnl {command}" and {input_method}\n{user_input}'))
@when( @when(re(f'we run "jrnl {command}" and {input_method} "{user_input}"'))
parsers.re( @when(re(f'we run "jrnl" and {input_method} "{user_input}"'))
'we run "jrnl (?P<command>[^"]+)" and (?P<input_method>enter|pipe) "(?P<user_input>[^"]+)"'
)
)
@when(parse('we run "jrnl" and {input_method} "{user_input}"'))
@when(parse('we run "jrnl {command}"')) @when(parse('we run "jrnl {command}"'))
@when('we run "jrnl <command>"')
@when('we run "jrnl"') @when('we run "jrnl"')
def we_run( def we_run_jrnl(cli_run, capsys, keyring):
command, from keyring import set_keyring
config_path,
config_in_memory,
user_input,
cli_run,
capsys,
password,
cache_dir,
editor,
keyring,
input_method,
mocks,
):
assert input_method in ["", "enter", "pipe"]
is_tty = input_method != "pipe"
if cache_dir["exists"]: set_keyring(keyring)
command = command.format(cache_dir=cache_dir["path"])
args = split_args(command)
status = 0
if user_input:
user_input = user_input.splitlines() if is_tty else [user_input]
if password:
password = password.splitlines()
if not password and user_input:
password = user_input
with ExitStack() as stack: with ExitStack() as stack:
# Always mock mocks = cli_run["mocks"]
from jrnl.override import apply_overrides factories = cli_run["mock_factories"]
def my_overrides(*args, **kwargs): for id in factories:
result = apply_overrides(*args, **kwargs) mocks[id] = stack.enter_context(factories[id]())
config_in_memory["overrides"] = result
return result
stack.enter_context(
patch("jrnl.jrnl.apply_overrides", side_effect=my_overrides)
)
# Conditionally mock
stack.enter_context(patch("sys.argv", ["jrnl"] + args))
mock_stdin = stack.enter_context(
patch("sys.stdin.read", side_effect=user_input)
)
stack.enter_context(patch("sys.stdin.isatty", return_value=is_tty))
mock_input = stack.enter_context(
patch("builtins.input", side_effect=user_input)
)
mock_getpass = stack.enter_context(
patch("getpass.getpass", side_effect=password)
)
if "datetime" in mocks:
stack.enter_context(mocks["datetime"])
stack.enter_context(mocks["calendar_parse"])
stack.enter_context(
patch("jrnl.install.get_config_path", return_value=config_path)
)
stack.enter_context(
patch("jrnl.config.get_config_path", return_value=config_path)
)
mock_editor = stack.enter_context(patch("subprocess.call", side_effect=editor))
try: try:
cli(args) cli()
except StopIteration: except StopIteration:
# This happens when input is expected, but don't have any input left # This happens when input is expected, but don't have any input left
pass pass
except SystemExit as e: except SystemExit as e:
status = e.code cli_run["status"] = e.code
captured = capsys.readouterr() captured = capsys.readouterr()
cli_run["status"] = status
cli_run["stdout"] = captured.out cli_run["stdout"] = captured.out
cli_run["stderr"] = captured.err cli_run["stderr"] = captured.err
cli_run["mocks"] = {
"stdin": mock_stdin,
"input": mock_input,
"getpass": mock_getpass,
"editor": mock_editor,
}