mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Merge pull request #3531 from InspectorCaracal/reports-contrib
Contrib for an in-game reports system
This commit is contained in:
commit
096100ee55
5 changed files with 664 additions and 0 deletions
128
evennia/contrib/base_systems/ingame_reports/README.md
Normal file
128
evennia/contrib/base_systems/ingame_reports/README.md
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
# In-Game Reporting System
|
||||
|
||||
This contrib provides an in-game reports system, handling bug reports, player reports, and idea submissions by default. It also supports adding your own types of reports, or removing any of the default report types.
|
||||
|
||||
Each type of report has its own command for submitting new reports, and an admin command is also provided for managing the reports through a menu.
|
||||
|
||||
## Installation
|
||||
|
||||
To install the reports contrib, just add the provided cmdset to your default AccountCmdSet:
|
||||
|
||||
```python
|
||||
# in commands/default_cmdset.py
|
||||
|
||||
from evennia.contrib.base_systems.ingame_reports import ReportsCmdSet
|
||||
|
||||
class AccountCmdSet(default_cmds.AccountCmdSet):
|
||||
# ...
|
||||
|
||||
def at_cmdset_creation(self):
|
||||
# ...
|
||||
self.add(ReportsCmdSet)
|
||||
```
|
||||
|
||||
The contrib also has two optional settings: `INGAME_REPORT_TYPES` and `INGAME_REPORT_STATUS_TAGS`.
|
||||
|
||||
The `INGAME_REPORT_TYPES` setting is covered in detail in the section "Adding new types of reports".
|
||||
|
||||
The `INGAME_REPORT_STATUS_TAGS` setting is covered in the section "Managing reports".
|
||||
|
||||
## Usage
|
||||
|
||||
By default, the following report types are available:
|
||||
|
||||
* Bugs: Report bugs encountered during gameplay.
|
||||
* Ideas: Submit suggestions for game improvement.
|
||||
* Players: Report inappropriate player behavior.
|
||||
|
||||
Players can submit new reports through the command for each report type, and staff are given access to a report-management command and menu.
|
||||
|
||||
### Submitting reports
|
||||
|
||||
Players can submit reports using the following commands:
|
||||
|
||||
* `bug <text>` - Files a bug report. An optional target can be included - `bug <target> = <text>` - making it easier for devs/builders to track down issues.
|
||||
* `report <player> = <text>` - Reports a player for inappropriate or rule-breaking behavior. *Requires* a target to be provided - it searches among accounts by default.
|
||||
* `idea <text>` - Submits a general suggestion, with no target. It also has an alias of `ideas` which allows you to view all of your submitted ideas.
|
||||
|
||||
### Managing reports
|
||||
|
||||
The `manage reports` command allows staff to review and manage the various types of reports by launching a management menu.
|
||||
|
||||
This command will dynamically add aliases to itself based on the types of reports available, with each command string launching a menu for that particular report type. The aliases are built on the pattern `manage <report type>s` - by default, this means it makes `manage bugs`, `manage players`, and `manage ideas` available along with the default `manage reports`, and that e.g. `manage bugs` will launch the management menu for `bug`-type reports.
|
||||
|
||||
Aside from reading over existing reports, the menu allows you to change the status of any given report. By default, the contrib includes two different status tags: `in progress` and `closed`.
|
||||
|
||||
> Note: A report is created with no status tags, which is considered "open"
|
||||
|
||||
If you want a different set of statuses for your reports, you can define the `INGAME_REPORT_STATUS_TAGS` to your list of statuses.
|
||||
|
||||
**Example**
|
||||
|
||||
```python
|
||||
# in server/conf/settings.py
|
||||
|
||||
# this will allow for the statuses of 'in progress', 'rejected', and 'completed', without the contrib-default of 'closed'
|
||||
INGAME_REPORT_STATUS_TAGS = ('in progress', 'rejected', 'completed')
|
||||
```
|
||||
|
||||
### Adding new types of reports
|
||||
|
||||
The contrib is designed to make adding new types of reports to the system as simple as possible, requiring only two steps:
|
||||
|
||||
1. Update your settings file to include an `INGAME_REPORT_TYPES` setting.
|
||||
2. Create and add a new `ReportCmd` to your command set.
|
||||
|
||||
#### Update your settings
|
||||
|
||||
The contrib optionally references `INGAME_REPORT_TYPES` in your settings.py to see which types of reports can be managed. If you want to change the available report types, you'll need to define this setting.
|
||||
|
||||
```python
|
||||
# in server/conf/settings.py
|
||||
|
||||
# this will include the contrib's report types as well as a custom 'complaint' report type
|
||||
INGAME_REPORT_TYPES = ('bugs', 'ideas', 'players', 'complaints')
|
||||
```
|
||||
|
||||
You can also use this setting to remove any of the contrib's report types - the contrib will respect this setting when building its cmdset with no additional steps.
|
||||
|
||||
```python
|
||||
# in server/conf/settings.py
|
||||
|
||||
# this redefines the setting to not include 'ideas', so the ideas command and reports won't be available
|
||||
INGAME_REPORT_TYPES = ('bugs', 'players')
|
||||
```
|
||||
|
||||
#### Create a new ReportCmd
|
||||
|
||||
`ReportCmdBase` is a parent command class which comes with the main functionality for submitting reports. Creating a new reporting command is as simple as inheriting from this class and defining a couple of class attributes.
|
||||
|
||||
* `key` - This is the same as for any other command, setting the command's usable key. It also acts as the report type if that isn't explicitly set.
|
||||
* `report_type` - The type of report this command is for (e.g. `player`). You only need to set it if you want a different string from the key.
|
||||
* `report_locks` - The locks you want applied to the created reports. Defaults to `"read:pperm(Admin)"`
|
||||
* `success_msg` - The string which is sent to players after submitting a report of this type. Defaults to `"Your report has been filed."`
|
||||
* `require_target`: Set to `True` if your report type requires a target (e.g. player reports).
|
||||
|
||||
> Note: The contrib's own commands - `CmdBug`, `CmdIdea`, and `CmdReport` - are implemented the same way, so you can review them as examples.
|
||||
|
||||
Example:
|
||||
|
||||
```python
|
||||
from evennia.contrib.base_systems.ingame_reports.reports import ReportCmdBase
|
||||
|
||||
class CmdCustomReport(ReportCmdBase):
|
||||
"""
|
||||
file a custom report
|
||||
|
||||
Usage:
|
||||
customreport <message>
|
||||
|
||||
This is a custom report type.
|
||||
"""
|
||||
|
||||
key = "customreport"
|
||||
report_type = "custom"
|
||||
success_message = "You have successfully filed a custom report."
|
||||
```
|
||||
|
||||
Add this new command to your default cmdset to enable filing your new report type.
|
||||
1
evennia/contrib/base_systems/ingame_reports/__init__.py
Normal file
1
evennia/contrib/base_systems/ingame_reports/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from .reports import ReportsCmdSet
|
||||
134
evennia/contrib/base_systems/ingame_reports/menu.py
Normal file
134
evennia/contrib/base_systems/ingame_reports/menu.py
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
"""
|
||||
The report-management menu module.
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from evennia.comms.models import Msg
|
||||
from evennia.utils import logger
|
||||
from evennia.utils.utils import crop, datetime_format, is_iter, iter_to_str
|
||||
|
||||
# the number of reports displayed on each page
|
||||
_REPORTS_PER_PAGE = 10
|
||||
|
||||
_REPORT_STATUS_TAGS = ("closed", "in progress")
|
||||
if hasattr(settings, "INGAME_REPORT_STATUS_TAGS"):
|
||||
if is_iter(settings.INGAME_REPORT_STATUS_TAGS):
|
||||
_REPORT_STATUS_TAGS = settings.INGAME_REPORT_STATUS_TAGS
|
||||
else:
|
||||
logger.log_warn(
|
||||
"The 'INGAME_REPORT_STATUS_TAGS' setting must be an iterable of strings; falling back to defaults."
|
||||
)
|
||||
|
||||
|
||||
def menunode_list_reports(caller, raw_string, **kwargs):
|
||||
"""Paginates and lists out reports for the provided hub"""
|
||||
hub = caller.ndb._evmenu.hub
|
||||
|
||||
page = kwargs.get("page", 0)
|
||||
start = page * _REPORTS_PER_PAGE
|
||||
end = start + _REPORTS_PER_PAGE
|
||||
report_slice = report_list[start:end]
|
||||
hub_name = " ".join(hub.key.split("_")).title()
|
||||
text = f"Managing {hub_name}"
|
||||
|
||||
if not (report_list := getattr(caller.ndb._evmenu, "report_list", None)):
|
||||
report_list = Msg.objects.search_message(receiver=hub).order_by("db_date_created")
|
||||
caller.ndb._evmenu.report_list = report_list
|
||||
# allow the menu to filter print-outs by status
|
||||
if kwargs.get("status"):
|
||||
new_report_list = report_list.filter(db_tags__db_key=kwargs["status"])
|
||||
# we don't filter reports if there are no reports under that filter
|
||||
if not new_report_list:
|
||||
text = f"(No {kwargs['status']} reports)\n{text}"
|
||||
else:
|
||||
report_list = new_report_list
|
||||
text = f"Managing {kwargs['status']} {hub_name}"
|
||||
else:
|
||||
report_list = report_list.exclude(db_tags__db_key="closed")
|
||||
|
||||
# filter by lock access
|
||||
report_list = [msg for msg in report_list if msg.access(caller, "read")]
|
||||
|
||||
# this will catch both no reports filed and no permissions
|
||||
if not report_list:
|
||||
return "There is nothing there for you to manage.", {}
|
||||
|
||||
options = [
|
||||
{
|
||||
"desc": f"{datetime_format(report.date_created)} - {crop(report.message, 50)}",
|
||||
"goto": ("menunode_manage_report", {"report": report}),
|
||||
}
|
||||
for report in report_slice
|
||||
]
|
||||
options.append(
|
||||
{
|
||||
"key": ("|uF|nilter by status", "filter", "status", "f"),
|
||||
"goto": "menunode_choose_filter",
|
||||
}
|
||||
)
|
||||
if start > 0:
|
||||
options.append(
|
||||
{
|
||||
"key": (f"|uP|nrevious {_REPORTS_PER_PAGE}", "previous", "prev", "p"),
|
||||
"goto": (
|
||||
"menunode_list_reports",
|
||||
{"page": max(start - _REPORTS_PER_PAGE, 0) // _REPORTS_PER_PAGE},
|
||||
),
|
||||
}
|
||||
)
|
||||
if end < len(report_list):
|
||||
options.append(
|
||||
{
|
||||
"key": (f"|uN|next {_REPORTS_PER_PAGE}", "next", "n"),
|
||||
"goto": (
|
||||
"menunode_list_reports",
|
||||
{"page": (start + _REPORTS_PER_PAGE) // _REPORTS_PER_PAGE},
|
||||
),
|
||||
}
|
||||
)
|
||||
return text, options
|
||||
|
||||
|
||||
def menunode_choose_filter(caller, raw_string, **kwargs):
|
||||
"""apply or clear a status filter to the main report view"""
|
||||
text = "View which reports?"
|
||||
# options for all the possible statuses
|
||||
options = [
|
||||
{"desc": status, "goto": ("menunode_list_reports", {"status": status})}
|
||||
for status in _REPORT_STATUS_TAGS
|
||||
]
|
||||
# no filter
|
||||
options.append({"desc": "All open reports", "goto": "menunode_list_reports"})
|
||||
return text, options
|
||||
|
||||
|
||||
def _report_toggle_tag(caller, raw_string, report, tag, **kwargs):
|
||||
"""goto callable to toggle a status tag on or off"""
|
||||
if tag in report.tags.all():
|
||||
report.tags.remove(tag)
|
||||
else:
|
||||
report.tags.add(tag)
|
||||
return ("menunode_manage_report", {"report": report})
|
||||
|
||||
|
||||
def menunode_manage_report(caller, raw_string, report, **kwargs):
|
||||
"""
|
||||
Read out the full report text and targets, and allow for changing the report's status.
|
||||
"""
|
||||
receivers = [r for r in report.receivers if r != caller.ndb._evmenu.hub]
|
||||
text = f"""\
|
||||
{report.message}
|
||||
{datetime_format(report.date_created)} by {iter_to_str(report.senders)}{' about '+iter_to_str(r.get_display_name(caller) for r in receivers) if receivers else ''}
|
||||
{iter_to_str(report.tags.all())}"""
|
||||
|
||||
options = []
|
||||
for tag in _REPORT_STATUS_TAGS:
|
||||
options.append(
|
||||
{
|
||||
"desc": f"{'Unmark' if tag in report.tags.all() else 'Mark' } as {tag}",
|
||||
"goto": (_report_toggle_tag, {"report": report, "tag": tag}),
|
||||
}
|
||||
)
|
||||
options.append({"desc": f"Manage another report", "goto": "menunode_list_reports"})
|
||||
return text, options
|
||||
315
evennia/contrib/base_systems/ingame_reports/reports.py
Normal file
315
evennia/contrib/base_systems/ingame_reports/reports.py
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
"""
|
||||
In-Game Reporting System
|
||||
|
||||
This contrib provides an in-game reporting system, with player-facing commands and a staff
|
||||
management interface.
|
||||
|
||||
# Installation
|
||||
|
||||
To install, just add the provided cmdset to your default AccountCmdSet:
|
||||
|
||||
# in commands/default_cmdset.py
|
||||
|
||||
from evennia.contrib.base_systems.ingame_reports import ReportsCmdSet
|
||||
|
||||
class AccountCmdSet(default_cmds.AccountCmdSet):
|
||||
# ...
|
||||
|
||||
def at_cmdset_creation(self):
|
||||
# ...
|
||||
self.add(ReportsCmdSet)
|
||||
|
||||
# Features
|
||||
|
||||
The contrib provides three commands by default and their associated report types: `CmdBug`, `CmdIdea`,
|
||||
and `CmdReport` (which is for reporting other players).
|
||||
|
||||
The `ReportCmdBase` class holds most of the functionality for creating new reports, providing a
|
||||
convenient parent class for adding your own categories of reports.
|
||||
|
||||
The contrib can be further configured through two settings, `INGAME_REPORT_TYPES` and `INGAME_REPORT_STATUS_TAGS`
|
||||
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from evennia import CmdSet
|
||||
from evennia.utils import create, evmenu, logger, search
|
||||
from evennia.utils.utils import class_from_module, datetime_format, is_iter, iter_to_str
|
||||
from evennia.commands.default.muxcommand import MuxCommand
|
||||
from evennia.comms.models import Msg
|
||||
|
||||
from . import menu
|
||||
|
||||
_DEFAULT_COMMAND_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||
|
||||
# the default report types
|
||||
_REPORT_TYPES = ("bugs", "ideas", "players")
|
||||
if hasattr(settings, "INGAME_REPORT_TYPES"):
|
||||
if is_iter(settings.INGAME_REPORT_TYPES):
|
||||
_REPORT_TYPES = settings.INGAME_REPORT_TYPES
|
||||
else:
|
||||
logger.log_warn(
|
||||
"The 'INGAME_REPORT_TYPES' setting must be an iterable of strings; falling back to defaults."
|
||||
)
|
||||
|
||||
|
||||
def _get_report_hub(report_type):
|
||||
"""
|
||||
A helper function to retrieve the global script which acts as the hub for a given report type.
|
||||
|
||||
Args:
|
||||
report_type (str): The category of reports to retrieve the script for.
|
||||
|
||||
Returns:
|
||||
Script or None: The global script, or None if it couldn't be retrieved or created
|
||||
|
||||
Note: If no matching valid script exists, this function will attempt to create it.
|
||||
"""
|
||||
hub_key = f"{report_type}_reports"
|
||||
# NOTE: due to a regression in GLOBAL_SCRIPTS, we use search_script instead of the container
|
||||
if not (hub := search.search_script(hub_key)):
|
||||
hub = create.create_script(key=hub_key)
|
||||
return hub or None
|
||||
|
||||
|
||||
class CmdManageReports(_DEFAULT_COMMAND_CLASS):
|
||||
"""
|
||||
manage the various reports
|
||||
|
||||
Usage:
|
||||
manage [report type]
|
||||
|
||||
Available report types:
|
||||
bugs
|
||||
ideas
|
||||
players
|
||||
|
||||
Initializes a menu for reviewing and changing the status of current reports.
|
||||
"""
|
||||
|
||||
key = "manage reports"
|
||||
aliases = tuple(f"manage {report_type}" for report_type in _REPORT_TYPES)
|
||||
locks = "cmd:pperm(Admin)"
|
||||
|
||||
def get_help(self):
|
||||
"""Returns a help string containing the configured available report types"""
|
||||
|
||||
report_types = iter_to_str("\n ".join(_REPORT_TYPES))
|
||||
|
||||
helptext = f"""\
|
||||
manage the various reports
|
||||
|
||||
Usage:
|
||||
manage [report type]
|
||||
|
||||
Available report types:
|
||||
{report_types}
|
||||
|
||||
Initializes a menu for reviewing and changing the status of current reports.
|
||||
"""
|
||||
|
||||
return helptext
|
||||
|
||||
def func(self):
|
||||
report_type = self.cmdstring.split()[-1]
|
||||
if report_type == "reports":
|
||||
report_type = "players"
|
||||
if report_type not in _REPORT_TYPES:
|
||||
self.msg(f"'{report_type}' is not a valid report category.")
|
||||
return
|
||||
# remove the trailing s, just so everything reads nicer
|
||||
report_type = report_type[:-1]
|
||||
hub = _get_report_hub(report_type)
|
||||
if not hub:
|
||||
self.msg("You cannot manage that.")
|
||||
|
||||
evmenu.EvMenu(
|
||||
self.account, menu, startnode="menunode_list_reports", hub=hub, persistent=True
|
||||
)
|
||||
|
||||
|
||||
class ReportCmdBase(_DEFAULT_COMMAND_CLASS):
|
||||
"""
|
||||
A parent class for creating report commands. This help text may be displayed if
|
||||
your command's help text is not properly configured.
|
||||
"""
|
||||
|
||||
help_category = "reports"
|
||||
# defines what locks the reports generated by this command will have set
|
||||
report_locks = "read:pperm(Admin)"
|
||||
# determines if the report can be filed without a target
|
||||
require_target = False
|
||||
# the message sent to the reporter after the report has been created
|
||||
success_msg = "Your report has been filed."
|
||||
# the report type for this command, if different from the key
|
||||
report_type = None
|
||||
|
||||
def at_pre_cmd(self):
|
||||
"""validate that the needed hub script exists - if not, cancel the command"""
|
||||
hub = _get_report_hub(self.report_type or self.key)
|
||||
if not hub:
|
||||
# a return value of True from `at_pre_cmd` cancels the command
|
||||
return True
|
||||
self.hub = hub
|
||||
return super().at_pre_cmd()
|
||||
|
||||
def parse(self):
|
||||
"""
|
||||
Parse the target and message out of the arguments.
|
||||
|
||||
Override if you want different syntax, but make sure to assign `report_message` and `target_str`.
|
||||
"""
|
||||
# do the base MuxCommand parsing first
|
||||
super().parse()
|
||||
# split out the report message and target strings
|
||||
if self.rhs:
|
||||
self.report_message = self.rhs
|
||||
self.target_str = self.lhs
|
||||
else:
|
||||
self.report_message = self.lhs
|
||||
self.target_str = ""
|
||||
|
||||
def target_search(self, searchterm, **kwargs):
|
||||
"""
|
||||
Search for a target that matches the given search term. By default, does a normal search via the
|
||||
caller - a local object search for a Character, or an account search for an Account.
|
||||
|
||||
Args:
|
||||
searchterm (str) - The string to search for
|
||||
|
||||
Returns:
|
||||
result (Object, Account, or None) - the result of the search
|
||||
"""
|
||||
return self.caller.search(searchterm)
|
||||
|
||||
def create_report(self, *args, **kwargs):
|
||||
"""
|
||||
Creates the report. By default, this creates a Msg with any provided args and kwargs.
|
||||
|
||||
Returns:
|
||||
success (bool) - True if the report was created successfully, or False if there was an issue.
|
||||
"""
|
||||
return create.create_message(*args, **kwargs)
|
||||
|
||||
def func(self):
|
||||
hub = self.hub
|
||||
if not self.args:
|
||||
self.msg("You must provide a message.")
|
||||
return
|
||||
|
||||
target = None
|
||||
if self.target_str:
|
||||
target = self.target_search(self.target_str)
|
||||
if not target:
|
||||
return
|
||||
elif self.require_target:
|
||||
self.msg("You must include a target.")
|
||||
return
|
||||
|
||||
receivers = [hub]
|
||||
if target:
|
||||
receivers.append(target)
|
||||
|
||||
if self.create_report(
|
||||
self.account, self.report_message, receivers=receivers, locks=self.report_locks, tags=["report"]
|
||||
):
|
||||
# the report Msg was successfully created
|
||||
self.msg(self.success_msg)
|
||||
else:
|
||||
# something went wrong
|
||||
self.msg(
|
||||
"Something went wrong creating your report. Please try again later or contact staff directly."
|
||||
)
|
||||
|
||||
|
||||
# The commands below are the usable reporting commands
|
||||
|
||||
|
||||
class CmdBug(ReportCmdBase):
|
||||
"""
|
||||
file a bug
|
||||
|
||||
Usage:
|
||||
bug [<target> =] <message>
|
||||
|
||||
Note: If a specific object, location or character is bugged, please target it for the report.
|
||||
|
||||
Examples:
|
||||
bug hammer = This doesn't work as a crafting tool but it should
|
||||
bug every time I go through a door I get the message twice
|
||||
"""
|
||||
|
||||
key = "bug"
|
||||
report_locks = "read:pperm(Developer)"
|
||||
|
||||
|
||||
class CmdReport(ReportCmdBase):
|
||||
"""
|
||||
report a player
|
||||
|
||||
Usage:
|
||||
report <player> = <message>
|
||||
|
||||
All player reports will be reviewed.
|
||||
"""
|
||||
|
||||
key = "report"
|
||||
report_type = "player"
|
||||
require_target = True
|
||||
account_caller = True
|
||||
|
||||
|
||||
class CmdIdea(ReportCmdBase):
|
||||
"""
|
||||
submit a suggestion
|
||||
|
||||
Usage:
|
||||
ideas
|
||||
idea <message>
|
||||
|
||||
Example:
|
||||
idea wouldn't it be cool if we had horses we could ride
|
||||
"""
|
||||
|
||||
key = "idea"
|
||||
aliases = ("ideas",)
|
||||
report_locks = "read:pperm(Builder)"
|
||||
success_msg = "Thank you for your suggestion!"
|
||||
|
||||
def func(self):
|
||||
# we add an extra feature to this command, allowing you to see all your submitted ideas
|
||||
if self.cmdstring == "ideas":
|
||||
# list your ideas
|
||||
if (
|
||||
ideas := Msg.objects.search_message(sender=self.account, receiver=self.hub)
|
||||
.order_by("-db_date_created")
|
||||
.exclude(db_tags__db_key="closed")
|
||||
):
|
||||
# todo: use a paginated menu
|
||||
self.msg(
|
||||
"Ideas you've submitted:\n "
|
||||
+ "\n ".join(
|
||||
f"|w{item.message}|n (submitted {datetime_format(item.date_created)})"
|
||||
for item in ideas
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.msg("You have no open suggestions.")
|
||||
return
|
||||
# proceed to do the normal report-command functionality
|
||||
super().func()
|
||||
|
||||
|
||||
class ReportsCmdSet(CmdSet):
|
||||
key = "Reports CmdSet"
|
||||
|
||||
def at_cmdset_creation(self):
|
||||
super().at_cmdset_creation()
|
||||
if "bugs" in _REPORT_TYPES:
|
||||
self.add(CmdBug)
|
||||
if "ideas" in _REPORT_TYPES:
|
||||
self.add(CmdIdea)
|
||||
if "players" in _REPORT_TYPES:
|
||||
self.add(CmdReport)
|
||||
self.add(CmdManageReports)
|
||||
86
evennia/contrib/base_systems/ingame_reports/tests.py
Normal file
86
evennia/contrib/base_systems/ingame_reports/tests.py
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
from unittest.mock import Mock, patch, MagicMock
|
||||
from evennia.utils import create
|
||||
from evennia.comms.models import TempMsg
|
||||
from evennia.utils.test_resources import EvenniaCommandTest
|
||||
|
||||
from . import menu, reports
|
||||
|
||||
|
||||
class _MockQuerySet(list):
|
||||
def order_by(self, *args, **kwargs):
|
||||
return self
|
||||
|
||||
def exclude(self, *args, **kwargs):
|
||||
return self
|
||||
|
||||
def filter(self, *args, **kwargs):
|
||||
return self
|
||||
|
||||
|
||||
def _mock_pre(cmdobj):
|
||||
"""helper to mock at_pre_cmd"""
|
||||
cmdobj.hub = Mock()
|
||||
|
||||
|
||||
class TestReportCommands(EvenniaCommandTest):
|
||||
@patch.object(create, "create_message", new=MagicMock())
|
||||
def test_report_cmd_base(self):
|
||||
"""verify that the base command functionality works"""
|
||||
cmd = reports.ReportCmdBase
|
||||
|
||||
# avoid test side-effects
|
||||
with patch.object(cmd, "at_pre_cmd", new=_mock_pre) as _:
|
||||
# no arguments
|
||||
self.call(cmd(), "", "You must provide a message.")
|
||||
# arguments, no target, no target required
|
||||
self.call(cmd(), "test", "Your report has been filed.")
|
||||
# arguments, custom success message
|
||||
custom_success = "custom success message"
|
||||
cmd.success_msg = custom_success
|
||||
self.call(cmd(), "test", custom_success)
|
||||
# arguments, no target, target required
|
||||
cmd.require_target = True
|
||||
self.call(cmd(), "test", "You must include a target.")
|
||||
|
||||
@patch.object(create, "create_message", new=MagicMock())
|
||||
@patch.object(reports, "datetime_format", return_value="now")
|
||||
def test_ideas_list(self, mock_datetime_format):
|
||||
cmd = reports.CmdIdea
|
||||
|
||||
fake_ideas = _MockQuerySet([TempMsg(message=f"idea {i+1}") for i in range(3)])
|
||||
expected = """\
|
||||
Ideas you've submitted:
|
||||
idea 1 (submitted now)
|
||||
idea 2 (submitted now)
|
||||
idea 3 (submitted now)
|
||||
"""
|
||||
|
||||
with patch.object(cmd, "at_pre_cmd", new=_mock_pre) as _:
|
||||
# submitting an idea
|
||||
self.call(cmd(), "", "You must provide a message.")
|
||||
# arguments, no target, no target required
|
||||
self.call(cmd(), "test", "Thank you for your suggestion!")
|
||||
|
||||
# viewing your submitted ideas
|
||||
with patch.object(reports.Msg.objects, "search_message", return_value=fake_ideas):
|
||||
self.call(cmd(), "", cmdstring="ideas", msg=expected)
|
||||
|
||||
@patch.object(reports.evmenu, "EvMenu")
|
||||
def test_cmd_manage_reports(self, evmenu_mock):
|
||||
cmd = reports.CmdManageReports
|
||||
hub = Mock()
|
||||
|
||||
with patch.object(reports, "_get_report_hub", return_value=hub) as _:
|
||||
# invalid report type fails
|
||||
self.call(
|
||||
cmd(), "", cmdstring="manage custom", msg="'custom' is not a valid report category."
|
||||
)
|
||||
# verify valid type triggers evmenu
|
||||
self.call(cmd(), "", cmdstring="manage bugs")
|
||||
evmenu_mock.assert_called_once_with(
|
||||
self.account,
|
||||
menu,
|
||||
startnode="menunode_list_reports",
|
||||
hub=hub,
|
||||
persistent=True,
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue