diff options
| -rw-r--r-- | modules/transcription-config.el | 68 | ||||
| -rw-r--r-- | tests/test-transcription-api-key.el | 115 |
2 files changed, 93 insertions, 90 deletions
diff --git a/modules/transcription-config.el b/modules/transcription-config.el index 8e3eb798..2adbaa4d 100644 --- a/modules/transcription-config.el +++ b/modules/transcription-config.el @@ -62,6 +62,20 @@ Log files are always kept on error regardless of this setting.") Each entry: (process audio-file start-time status) Status: running, complete, error") +;; ---------------------------- Backend Descriptors --------------------------- + +(defconst cj/--transcription-backends + '((openai-api :script "oai-transcribe" :auth-host "api.openai.com" :env-var "OPENAI_API_KEY") + (assemblyai :script "assemblyai-transcribe" :auth-host "api.assemblyai.com" :env-var "ASSEMBLYAI_API_KEY") + (local-whisper :script "local-whisper" :auth-host nil :env-var nil)) + "Per-backend descriptors. Each entry: (SYMBOL :script S :auth-host H :env-var V). +`:auth-host' and `:env-var' are nil for local backends that need no API key.") + +(defun cj/--backend-plist (backend) + "Return the descriptor plist for BACKEND, or signal if unknown." + (or (alist-get backend cj/--transcription-backends) + (user-error "Unknown transcription backend: %s" backend))) + ;; ----------------------------- Pure Functions -------------------------------- (defun cj/--audio-file-p (file) @@ -90,34 +104,15 @@ SUCCESS-P indicates whether transcription succeeded." cj/transcription-keep-log-when-done)) (defun cj/--transcription-script-path () - "Return absolute path to transcription script based on backend." - (let ((script-name (pcase cj/transcribe-backend - ('openai-api "oai-transcribe") - ('assemblyai "assemblyai-transcribe") - ('local-whisper "local-whisper")))) + "Return absolute path to transcription script for the active backend." + (let ((script-name (plist-get (cj/--backend-plist cj/transcribe-backend) :script))) (expand-file-name (concat "scripts/" script-name) user-emacs-directory))) -(defun cj/--get-openai-api-key () - "Retrieve OpenAI API key from authinfo.gpg. -Expects entry in authinfo.gpg: - machine api.openai.com login api password sk-... -Returns the API key string, or nil if not found." - (when-let* ((auth-info (car (auth-source-search - :host "api.openai.com" - :require '(:secret)))) - (secret (plist-get auth-info :secret))) - (if (functionp secret) - (funcall secret) - secret))) - -(defun cj/--get-assemblyai-api-key () - "Retrieve AssemblyAI API key from authinfo.gpg. -Expects entry in authinfo.gpg: - machine api.assemblyai.com login api password <key> -Returns the API key string, or nil if not found." - (when-let* ((auth-info (car (auth-source-search - :host "api.assemblyai.com" - :require '(:secret)))) +(defun cj/--auth-source-password (host) + "Retrieve password for HOST from authinfo.gpg. +Expects entry like: machine HOST login api password <key>. +Returns the password string, or nil if no matching entry exists." + (when-let* ((auth-info (car (auth-source-search :host host :require '(:secret)))) (secret (plist-get auth-info :secret))) (if (functionp secret) (funcall secret) @@ -164,19 +159,14 @@ Returns the process object." ;; Start process with environment (let* ((process-environment - ;; Add API key to environment based on backend - (pcase cj/transcribe-backend - ('openai-api - (if-let ((api-key (cj/--get-openai-api-key))) - (cons (format "OPENAI_API_KEY=%s" api-key) - process-environment) - (user-error "OpenAI API key not found in authinfo.gpg for host api.openai.com"))) - ('assemblyai - (if-let ((api-key (cj/--get-assemblyai-api-key))) - (cons (format "ASSEMBLYAI_API_KEY=%s" api-key) - process-environment) - (user-error "AssemblyAI API key not found in authinfo.gpg for host api.assemblyai.com"))) - (_ process-environment))) + (let* ((desc (cj/--backend-plist cj/transcribe-backend)) + (auth-host (plist-get desc :auth-host)) + (env-var (plist-get desc :env-var))) + (if (and auth-host env-var) + (if-let ((api-key (cj/--auth-source-password auth-host))) + (cons (format "%s=%s" env-var api-key) process-environment) + (user-error "API key not found in authinfo.gpg for host %s" auth-host)) + process-environment))) (process (make-process :name process-name :buffer (get-buffer-create buffer-name) diff --git a/tests/test-transcription-api-key.el b/tests/test-transcription-api-key.el index 01a7a0ea..fb0fbf5a 100644 --- a/tests/test-transcription-api-key.el +++ b/tests/test-transcription-api-key.el @@ -1,10 +1,9 @@ -;;; test-transcription-api-key.el --- Tests for API-key retrieval -*- lexical-binding: t; -*- +;;; test-transcription-api-key.el --- Tests for auth-source password helper -*- lexical-binding: t; -*- ;;; Commentary: -;; Characterization tests for `cj/--get-openai-api-key' and -;; `cj/--get-assemblyai-api-key'. Pin down current auth-source-search -;; behavior so the upcoming consolidation into a single parameterized -;; helper can be verified as a pure refactor. +;; Tests for `cj/--auth-source-password', the parameterized helper that +;; replaced the per-backend `cj/--get-openai-api-key' and +;; `cj/--get-assemblyai-api-key' functions. ;;; Code: @@ -18,14 +17,13 @@ (require 'transcription-config) -;; Captures the most recent :host passed to auth-source-search so each -;; test can assert that the right host was queried. -(defvar test-api-key-requested-host nil) +(defvar test-api-key-requested-host nil + "Captures the :host passed to the mocked `auth-source-search'.") (defmacro test-api-key-with-auth-source (secret-value &rest body) "Run BODY with `auth-source-search' stubbed to return SECRET-VALUE. -SECRET-VALUE is either a string, a function returning a string, or nil -\(to simulate no matching entry). The host passed to `auth-source-search' +SECRET-VALUE is a string, a function returning a string, or nil (to +simulate no matching entry). The host passed to `auth-source-search' is captured in `test-api-key-requested-host'." (declare (indent 1)) `(let ((test-api-key-requested-host nil)) @@ -36,55 +34,70 @@ is captured in `test-api-key-requested-host'." (list (list :secret ,secret-value)))))) ,@body))) -;;; Normal Cases — OpenAI +;;; Normal Cases -(ert-deftest test-api-key-openai-normal-returns-string-secret () - "OpenAI key retrieval returns the string stored under :secret." +(ert-deftest test-api-key-normal-returns-string-secret () + "Returns the :secret value when stored as a string." (test-api-key-with-auth-source "sk-abc123" - (should (equal "sk-abc123" (cj/--get-openai-api-key))) - (should (equal "api.openai.com" test-api-key-requested-host)))) + (should (equal "sk-abc123" (cj/--auth-source-password "api.openai.com"))))) -(ert-deftest test-api-key-openai-normal-calls-function-secret () - "OpenAI key retrieval invokes :secret when it's a function." - (test-api-key-with-auth-source (lambda () "sk-from-fn") - (should (equal "sk-from-fn" (cj/--get-openai-api-key))))) - -;;; Normal Cases — AssemblyAI - -(ert-deftest test-api-key-assemblyai-normal-returns-string-secret () - "AssemblyAI key retrieval returns the string stored under :secret." - (test-api-key-with-auth-source "aai-xyz789" - (should (equal "aai-xyz789" (cj/--get-assemblyai-api-key))) - (should (equal "api.assemblyai.com" test-api-key-requested-host)))) - -(ert-deftest test-api-key-assemblyai-normal-calls-function-secret () - "AssemblyAI key retrieval invokes :secret when it's a function." +(ert-deftest test-api-key-normal-calls-function-secret () + "Invokes the :secret value when stored as a function." (test-api-key-with-auth-source (lambda () "aai-from-fn") - (should (equal "aai-from-fn" (cj/--get-assemblyai-api-key))))) + (should (equal "aai-from-fn" (cj/--auth-source-password "api.assemblyai.com"))))) + +(ert-deftest test-api-key-normal-passes-host-through () + "The caller-supplied HOST is forwarded to `auth-source-search'." + (test-api-key-with-auth-source "x" + (cj/--auth-source-password "api.openai.com") + (should (equal "api.openai.com" test-api-key-requested-host))) + (test-api-key-with-auth-source "x" + (cj/--auth-source-password "api.assemblyai.com") + (should (equal "api.assemblyai.com" test-api-key-requested-host))) + (test-api-key-with-auth-source "x" + (cj/--auth-source-password "example.com") + (should (equal "example.com" test-api-key-requested-host)))) ;;; Boundary Cases -(ert-deftest test-api-key-openai-boundary-returns-nil-when-entry-missing () - "OpenAI key retrieval returns nil when auth-source has no matching entry." - (test-api-key-with-auth-source nil - (should (null (cj/--get-openai-api-key))))) - -(ert-deftest test-api-key-assemblyai-boundary-returns-nil-when-entry-missing () - "AssemblyAI key retrieval returns nil when auth-source has no matching entry." +(ert-deftest test-api-key-boundary-returns-nil-when-entry-missing () + "Returns nil when `auth-source-search' finds no matching entry." (test-api-key-with-auth-source nil - (should (null (cj/--get-assemblyai-api-key))))) - -(ert-deftest test-api-key-openai-boundary-queries-correct-host () - "OpenAI key retrieval always queries `api.openai.com', never another host." - (test-api-key-with-auth-source "sk-x" - (cj/--get-openai-api-key) - (should (equal "api.openai.com" test-api-key-requested-host)))) - -(ert-deftest test-api-key-assemblyai-boundary-queries-correct-host () - "AssemblyAI key retrieval always queries `api.assemblyai.com'." - (test-api-key-with-auth-source "aai-x" - (cj/--get-assemblyai-api-key) - (should (equal "api.assemblyai.com" test-api-key-requested-host)))) + (should (null (cj/--auth-source-password "api.openai.com"))) + (should (null (cj/--auth-source-password "api.assemblyai.com"))))) + +(ert-deftest test-api-key-boundary-empty-string-host () + "Empty host string is forwarded unchanged (auth-source decides)." + (test-api-key-with-auth-source "x" + (cj/--auth-source-password "") + (should (equal "" test-api-key-requested-host)))) + +;;; Backend Descriptor Integration + +(ert-deftest test-api-key-integration-openai-backend-descriptor () + "openai-api backend descriptor has the expected auth-host and env-var." + (let ((desc (cj/--backend-plist 'openai-api))) + (should (equal "api.openai.com" (plist-get desc :auth-host))) + (should (equal "OPENAI_API_KEY" (plist-get desc :env-var))) + (should (equal "oai-transcribe" (plist-get desc :script))))) + +(ert-deftest test-api-key-integration-assemblyai-backend-descriptor () + "assemblyai backend descriptor has the expected auth-host and env-var." + (let ((desc (cj/--backend-plist 'assemblyai))) + (should (equal "api.assemblyai.com" (plist-get desc :auth-host))) + (should (equal "ASSEMBLYAI_API_KEY" (plist-get desc :env-var))) + (should (equal "assemblyai-transcribe" (plist-get desc :script))))) + +(ert-deftest test-api-key-integration-local-whisper-needs-no-key () + "local-whisper descriptor explicitly has nil auth-host and env-var." + (let ((desc (cj/--backend-plist 'local-whisper))) + (should (null (plist-get desc :auth-host))) + (should (null (plist-get desc :env-var))) + (should (equal "local-whisper" (plist-get desc :script))))) + +(ert-deftest test-api-key-integration-unknown-backend-errors () + "Requesting an unknown backend's descriptor signals user-error." + (should-error (cj/--backend-plist 'nonexistent-backend) :type 'user-error)) (provide 'test-transcription-api-key) ;;; test-transcription-api-key.el ends here |
