Repository: ReadAlongs/Studio (language: python)

Issue:
Web API PRODUCTION environment variable is unnecessarily obtuse
The way of telling the web API to go into development mode makes no sense:

```
PRODUCTION= uvicorn readalongs.web_api:web_api_app --reload
```

If production mode is the default then we should instead have a `DEVELOPMENT` environment variable that enables permissive CORS headers.

The `PRODUCTION` variable means nothing to uvicorn and it means nothing to FastAPI, we are the only ones using it.  Unless there is some dark Heroku magic involved?  But even then, why would it be production mode if (a) `PRODUCTION` is unset, *or* if (b) `PRODUCTION` is set to something truthy?


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index 173ee2c3..880fe3ce 100644
--- a/README.md
+++ b/README.md
@@ -161,7 +161,9 @@ This page lists only the most basic commands.
 
 For more information about how the command line interface works consult the interactive [API Documentation](https://readalong-studio.herokuapp.com/api/v1/docs).
 
-For information on spinning up your own dev Web API server locally, have a look at [web\_api.py](readalongs/web_api.py).
+For information on spinning up your own dev Web API server locally, have a look at [web\_api.py](readalongs/web_api.py), but briefly, if you are running it locally for development, use:
+
+    DEVELOPMENT=1 uvicorn readalongs.web_api:web_api_app --reload
 
 #### /langs
 
diff --git a/readalongs/web_api.py b/readalongs/web_api.py
index e1fd4eb3..082883d3 100644
--- a/readalongs/web_api.py
+++ b/readalongs/web_api.py
@@ -1,17 +1,23 @@
-"""
-REST-ish Web API for ReadAlongs Studio text manipulation operations using FastAPI.
+"""REST-ish Web API for ReadAlongs Studio text manipulation operations using FastAPI.
 
 See https://readalong-studio.herokuapp.com/api/v1/docs for the documentation.
 
 You can spin up this Web API for development purposes on any platform with:
     pip install uvicorn
     cd readalongs/
-    PRODUCTION= uvicorn readalongs.web_api:web_api_app --reload
+    DEVELOPMENT=1 uvicorn readalongs.web_api:web_api_app --reload
 - The --reload switch will watch for changes under the directory where it's
   running and reload the code whenever it changes, so it's best run in readalongs/
-- PRODUCTION= tells uvicorn to run in non-production mode, i.e., in debug mode,
-  and automatically add the header "access-control-allow-origin: *" to each
-  response so you won't get CORS errors using this locally with Studio-Web.
+- DEVELOPMENT=1 tells the API to accept cross-origin requests (i.e. by sending the
+  appropriate CORS headers) from development servers running on localhost, e.g.
+  http://localhost:4200
+
+For deployment, you can use the ORIGIN environment variable to set the URL of your
+application in order to make it accept requests from that site.  For instance if
+you deployed an application that uses it (such as Studio-Web) at
+https://my.awesome.site you would set ORIGIN=https://my.awesome.site in your
+environment variables.  This is usually done through an environment variable file
+(or in a dashboard) and will depend on your hosting environment.
 
 You can also spin up the API server grade (on Linux, not Windows) with gunicorn:
     pip install -r requirements.api.txt
@@ -19,6 +25,7 @@
 
 Once spun up, the documentation and API playground will be visible at
 http://localhost:8000/api/v1/docs
+
 """
 
 import io
@@ -47,25 +54,31 @@
 
 # Create the app
 web_api_app = FastAPI()
+middleware_args: Dict[str, Union[str, List[str]]]
+if os.getenv("DEVELOPMENT", False):
+    LOGGER.info(
+        "Running in development mode, will allow requests from http://localhost:*"
+    )
+    # Allow requests from localhost dev servers
+    middleware_args = dict(
+        allow_origin_regex="http://localhost(:.*)?",
+    )
+else:
+    # Allow requests *only* from mt app (or otherwise configured site name)
+    middleware_args = dict(
+        allow_origins=[
+            os.getenv("ORIGIN", "https://readalong-studio.mothertongues.org"),
+        ],
+    )
+web_api_app.add_middleware(
+    CORSMiddleware, allow_methods=["GET", "POST", "OPTIONS"], **middleware_args
+)
+
 # Create the v1 version of the API
 v1 = FastAPI()
 # Call get_langs() when the server loads to load the languages into memory
 LANGS = get_langs()
 
-if os.getenv("PRODUCTION", True):
-    origins = [
-        "https://readalong-studio.mothertongues.org",
-    ]  # Allow requests from mt app
-else:
-    origins = ["*"]  # Allow requests from any origin
-web_api_app.add_middleware(
-    CORSMiddleware,
-    allow_origins=origins,
-    allow_credentials=True,
-    allow_methods=["GET", "POST"],
-    allow_headers=["*"],
-)
-
 
 class RequestBase(BaseModel):
     """Base request for assemble"""


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 4b4b61f7..3f3960a6 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -33,8 +33,11 @@ jobs:
 
       - name: Run tests
         run: |
-          gunicorn readalongs.app:app --bind 0.0.0.0:5000 --daemon
-          cd test && coverage run run.py prod && coverage xml
+          cd test
+          coverage run --parallel-mode run.py prod
+          DEVELOPMENT=1 coverage run --parallel-mode test_web_api.py
+          coverage combine
+          coverage xml
 
       - name: Nitpicking
         run: |