﻿---
title: "Emacs - Básico 1"
date: 2023-07-09T21:54:27-05:00
draft: false
---

GNU EMACS es un editor altamente extensible y vamos aprovechar esta posibilidad
que nos ofrecen y construir un paquete que nos permita cubrir
varios elementos (peticiones HTTP, SON, TDD, EMACS), 
el ejercicio entorna a implementar un módulo de EMACS que nos permita obtener el titulo de una incidencia, procediendo con:

* Autenticar a Gitea
* Solicitar id de incidencia
* Consultar titulo de incidencia indicado
* E insertar titulo en el árbol del documento org.

importante antes de empezar:

* ser usuario de EMACS.
* nociones en desarrollo de software.
* nociones del lenguaje LISP o uno de sus dialectos.

Vamos a usar el [dialecto ELisp](https://www.gnu.org/software/emacs/manual/html_node/elisp/Introduction.html) de EMACS el cual iremos conociendo poco a poco en el transcurso de la implementación.

Empecemos por crear la estructura mínima del paquete.

**Makefile**
```makefile @mdtoapp file:Makefile
.PHONY: test
test:
	emacs --batch -L . -L tests -l grieta-tests.el -f ert-run-tests-batch
```

```sh @mdtoapp @execute
make test
```
```text
Cannot open load file: No such file or directory, grieta-tests.el
make: *** [Makefile:3: test] Error 255
```

Empecemos a ilustrarnos a partir del error

`Cannot open load file: No such file or directory, grieta-tests.el`

no ubica el archivo `grieta-tests.el`, debemos indicar el directorio donde se encuentra el archivo,
usamos los siguientes parametros.

* en el parametro `-L` le indicamos a `ENACS` donde ubicar archivos `Lisp`.
* en el parametro `-l` le indicamos interpretar el archivo `Lisp`.
* en el parametro `-f` le indicamos una función a ejecutar.

En otras palabras le estamos indicando a EMACS que ejecuta la función [ert-run-tests-batch](https://www.gnu.org/software/emacs/manual/html_node/ert/Running-Tests-in-Batch-Mode.html) además que interprete el archivo `grieta-tests.el` y que
posiblemente lo puede encontrar en el directorio `tests`.
.

**tests/grieta-tests.el**
```sh @mdtoapp @execute
mkdir tests
touch tests/grieta-tests.el
```
```ssh @mdtoapp @execute
make test
```
```text @output
Running 0 tests (2023-07-10 21:29:29-0500, selector ‘t’)

Ran 0 tests, 0 results as expected, 0 unexpected (2023-07-10 21:29:29-0500, 0.000060 sec)
```

muy bien no mas errores por ahora, ahora vamos a proceder a iniciar con las pruebas
y por medio de estas iremos realizando la implementación. Para automatizar las pruebas
utilizaremos [ERT](https://www.gnu.org/software/emacs/manual/html_node/ert/).

Para empezar definimos como agrupar las pruebas:


* `UNIT` funciones que solo varían su comportamiento según los argumentos.
* `INTEGRATION` funciones que varían su comportamiento según los argumentos u otros medios como variables globales o lecturas fuera del proceso (lectura disco, red, etc..).
* `ACCEPTANCE` los mismo criterios que `INTEGRATION` pero solo sobre las funciones publicadas es decir sobre las funciones que son accesible por el usuario u otros paquetes.
* `MANUAL` pruebas que se deben realizar manualmente ya que el contexto no se puede reproduccir en `ACCEPTANCE`.


**tests/grieta-tests.el**
```lisp @mdtoapp file:tests/grieta-tests.el
(require 'el-mock)

;; UNIT
;; INTEGRATION
;; ACCEPTANCE
;; MANUAL

(provide 'grieta-tests)
```
```sh @mdtoapp @execute
make test
```
```text @output
Cannot open load file: No such file or directory, el-mock
make: *** [Makefile:3: test] Error 255
```

Uumm ya hemos visto anteriormente este error, `el-mock` es una dependencia
que utilizaremos para verificar el comportamiento en las pruebas de `INTEGRATION` y `ACCEPTANCE`.

```sh @mdtoapp @execute
curl -q https://raw.githubusercontent.com/rejeep/el-mock.el/master/el-mock.el --output tests/el-mock.el
```
```sh @mdtoapp @execute
make test
```
```text @output
Running 0 tests (2023-07-10 21:46:25-0500, selector ‘t’)

Ran 0 tests, 0 results as expected, 0 unexpected (2023-07-10 21:46:25-0500, 0.000052 sec)
```

Excelente, ya no tenemos error al importar la dependencia `el-mock` con ([require](https://www.gnu.org/software/emacs/manual/html_node/elisp/Named-Features.html#index-require) 'el-mock).

**tests/grieta-tests.el**
```lisp @mdtoapp file:tests/grieta-tests.el
(require 'el-mock)

;; UNIT
;; INTEGRATION
(ert-deftest grieta-test-grieta--gitea-issue-check-url-path ()
  (with-mock
   ;; https://try.gitea.io/api/swagger#/issue/issueGetIssue
   (mock (grieta--gitea-call "gitea.test.org" "GET" "/api/v1/repos/demo/grieta/issues/99"))
   (grieta--gitea-issue "gitea.test.org" "demo" "grieta" 99)))

;; ACCEPTANCE
;; MANUAL
(provide 'grieta-tests)
```
```sh @mdtoapp @execute
make test
```
```text @output
Test grieta-test-grieta--gitea-issue-check-url-path condition:
    (void-function grieta--gitea-issue)
   FAILED  1/1  grieta-test-grieta--gitea-issue-check-url-path (0.000067 sec)
```

Iniciar las pruebas desde `INTEGRATION` o `ACCEPTANCE` nos facilita tener una perspectiva más cercana a su uso y empezar con anterioridad el flujo de la implementación, hemos decido empezar por confirmar que la petición este en la estructura
esperada por la API de gitea.

`(void-function grieta--gitea-issue)` lo vamos a ver cuando no logra llamar la función, en nuestro caso es por que no existe.

<!-- creacion funcion grieta--gitea-issue -->
**tests/grieta-tests.el**
```lisp @mdtoapp file:tests/grieta-tests.el
(require 'el-mock)

(defun grieta--gitea-issue (host owner repo issue-id))

;; UNIT
;; INTEGRATION
(ert-deftest grieta-test-grieta--gitea-issue-check-url-path ()
  (with-mock
   ;; https://try.gitea.io/api/swagger#/issue/issueGetIssue
   (mock (grieta--gitea-call "gitea.test.org" "GET" "/api/v1/repos/demo/grieta/issues/99"))
   (grieta--gitea-issue "gitea.test.org" "demo" "grieta" 99)))

;; ACCEPTANCE
;; MANUAL
(provide 'grieta-tests)
```
```sh @mdtoapp @execute
make test
```
```text @output
    (mock-error not-called grieta--gitea-call)
   FAILED  1/1  grieta-test-grieta--gitea-issue-check-url-path (0.000075 sec)
```

`(mock-error not-called grieta--gitea-call)` nos indica que se esperaba que se invocará esa función pero no sucedió.

<!-- creacion funcion grieta--gitea-call -->
**tests/grieta-tests.el**
```lisp @mdtoapp file:tests/grieta-tests.el
(require 'el-mock)

(defun grieta--gitea-call (host method path))
(defun grieta--gitea-issue (host owner repo issue-id)
  (grieta--gitea-call host "GET" (format "/api/v1/repos/%s/%s/issues/%d" owner repo issue-id)))

;; UNIT
;; INTEGRATION
(ert-deftest grieta-test-grieta--gitea-issue-check-url-path ()
  (with-mock
   ;; https://try.gitea.io/api/swagger#/issue/issueGetIssue
   (mock (grieta--gitea-call "gitea.test.org" "GET" "/api/v1/repos/demo/grieta/issues/99"))
   (grieta--gitea-issue "gitea.test.org" "demo" "grieta" 99)))

;; ACCEPTANCE
;; MANUAL
(provide 'grieta-tests)
```
```sh @mdtoapp @execute
make test
```
```text @output
Running 1 tests (2023-07-10 22:03:12-0500, selector ‘t’)
   passed  1/1  grieta-test-grieta--gitea-issue-check-url-path (0.000062 sec)

Ran 1 tests, 1 results as expected, 0 unexpected (2023-07-10 22:03:12-0500, 0.000143 sec)
```

Muy bien hemos ajustado según lo ilustrado por la prueba, ahora juguemos un poco en  
aclarar el código, las funciones que creamos `grieta--gitea-issue` y `grieta--gitea-call` están en las pruebas, movamos estas pruebas a su propio archivo.


**grieta.el**
```lisp @mdtoapp @create file:grieta.el
(defun grieta--gitea-call (host method path))
(defun grieta--gitea-issue (host owner repo issue-id)
  (grieta--gitea-call host "GET" (format "/api/v1/repos/%s/%s/issues/%d" owner repo issue-id)))

;; https://www.gnu.org/software/emacs/manual/html_node/elisp/Named-Features.html
;; `provide` es necesario para luego importar con `require`
(provide 'grieta)
```
**tests/grieta-tests.el**
```lisp @mdtoapp @create file:tests/grieta-tests.el
(require 'el-mock)

;; UNIT
;; INTEGRATION
(ert-deftest grieta-test-grieta--gitea-issue-check-url-path ()
  (with-mock
   ;; https://try.gitea.io/api/swagger#/issue/issueGetIssue
   (mock (grieta--gitea-call "gitea.test.org" "GET" "/api/v1/repos/demo/grieta/issues/99"))
   (grieta--gitea-issue "gitea.test.org" "demo" "grieta" 99)))

;; ACCEPTANCE
;; MANUAL
(provide 'grieta-tests)
```
```sh @mdtoapp @execute
make test
```
```text @output
Test grieta-test-grieta--gitea-issue-check-url-path condition:
    (void-function grieta--gitea-issue)
   FAILED  1/1  grieta-test-grieta--gitea-issue-check-url-path (0.000073 sec)
```

`(void-function grieta--gitea-issue)` ya hemos lo hemos vistos anteriormente, no logra ubicar
la función y claro es debido a que movimos la función a otro archivo, debemos
importar el archivo.

**tests/grieta-tests.el**
```lisp @mdtoapp @create file:tests/grieta-tests.el
(require 'el-mock)
(require 'grieta)

;; UNIT
;; INTEGRATION
(ert-deftest grieta-test-grieta--gitea-issue-check-url-path ()
  (with-mock
   ;; https://try.gitea.io/api/swagger#/issue/issueGetIssue
   (mock (grieta--gitea-call "gitea.test.org" "GET" "/api/v1/repos/demo/grieta/issues/99"))
   (grieta--gitea-issue "gitea.test.org" "demo" "grieta" 99)))

;; ACCEPTANCE
;; MANUAL
(provide 'grieta-tests)
```
```sh @mdtoapp @execute
make test
```
```output
Running 1 tests (2023-07-10 22:08:57-0500, selector ‘t’)
   passed  1/1  grieta-test-grieta--gitea-issue-check-url-path (0.000060 sec)

Ran 1 tests, 1 results as expected, 0 unexpected (2023-07-10 22:08:57-0500, 0.000137 sec)
```

Muy bien volvimos a estar estables, ya que empezamos por validar la estructura
de la URL podemos continuar sobre el mismo tema de validar elementos
hacia la API externa, procedamos por ejemplo con traducir la respuesta JSON a
una estructura interna.

**tests/grieta-tests.el**
```lisp @mdtoapp @create file:tests/grieta-tests.el
(require 'el-mock)
(require 'grieta)

;; UNIT
;; INTEGRATION
(ert-deftest grieta-test-grieta--gitea-json->issues ()
  (with-mock
   ;; https://try.gitea.io/api/swagger#/issue/issueGetIssue
   ;; respuesta simplificada
   (stub grieta--gitea-call => "{\"id\": 1000, \"number\": 1, \"title\": \"Test\"}")

   (should (equal
            '(1 "Test")
            (grieta--gitea-issue "gitea.test.org" "demo" "grieta" 1)))))
   
(ert-deftest grieta-test-grieta--gitea-issue-check-url-path ()
  (with-mock
   ;; https://try.gitea.io/api/swagger#/issue/issueGetIssue
   (mock (grieta--gitea-call "gitea.test.org" "GET" "/api/v1/repos/demo/grieta/issues/99"))
   (grieta--gitea-issue "gitea.test.org" "demo" "grieta" 99)))

;; ACCEPTANCE
;; MANUAL
(provide 'grieta-tests)
```
```sh @mdtoapp @execute
make test
```
```text @output
Test grieta-test-grieta--gitea-json->issues condition:
    (ert-test-failed
     ((should
       (equal '...
	(grieta--gitea-issue "gitea.test.org" "demo" "grieta" 1)))
      :form
      (equal
       ((1 "Test"))
       "{\"id\": 1000, \"number\": 1, \"title\": \"Test\"}")
      :value nil :explanation
      (different-types
       ((1 "Test"))
       "{\"id\": 1000, \"number\": 1, \"title\": \"Test\"}")))
   FAILED  2/2  grieta-test-grieta--gitea-json->issues (0.000051 sec)
```

miremos solo una parte
```text
      :form
      (equal
       ((1 "Test"))
       "{\"id\": 1000, \"number\": 1, \"title\": \"Test\"}")
```

evidentemente los resultados no son iguales estamos retornando directamente
la respuesta de la API, procedamos a implementar el traductor.

**grieta.el**
```lisp @mdtoapp @create file:grieta.el
(defun grieta--gitea-call (host method path))
(defun grieta--gitea-issue (host owner repo issue-id)
  (let* ((response (grieta--gitea-call host "GET" (format "/api/v1/repos/%s/%s/issues/%d" owner repo issue-id))))
    (if response
        (let ((remote-issue (json-parse-string response)))
          (list (gethash "number" remote-issue)
                (gethash "title" remote-issue)))
      nil)))

;; https://www.gnu.org/software/emacs/manual/html_node/elisp/Named-Features.html
;; `provide` es necesario para luego importar con `require`
(provide 'grieta)
```
```sh @mdtoapp @execute
make test
```
```text @output
Running 2 tests (2023-07-10 22:35:19-0500, selector ‘t’)
   passed  1/2  grieta-test-grieta--gitea-issue-check-url-path (0.000058 sec)
   passed  2/2  grieta-test-grieta--gitea-json->issues (0.000062 sec)

Ran 2 tests, 2 results as expected, 0 unexpected (2023-07-10 22:35:19-0500, 0.000208 sec
```

Hemos logrado estar otra vez estables, pero en este caso adicionamos un `if`
lo cual nos invita que debemos verificar cual sera el comportamiento cuando no se obtenga
una respuesta desde el servidor.

**tests/grieta-tests.el**
```lisp @mdtoapp @create file:tests/grieta-tests.el
(require 'el-mock)
(require 'grieta)

;; UNIT
;; INTEGRATION
(ert-deftest grieta-test-grieta--gitea-json->issues-on-valid-data ()
  (with-mock
   ;; https://try.gitea.io/api/swagger#/issue/issueGetIssue
   ;; respuesta simplificada
   (stub grieta--gitea-call => "{\"id\": 1000, \"number\": 1, \"title\": \"Test\"}")

   (should (equal
            '(1 "Test")
            (grieta--gitea-issue "gitea.test.org" "demo" "grieta" 1)))))

(ert-deftest grieta-test-grieta--gitea-json->issues-on-invalid-data ()
  (with-mock
   ;; https://try.gitea.io/api/swagger#/issue/issueGetIssue
   (stub grieta--gitea-call => nil)

   (should (equal
            nil ; volveremos a revisar luego si esta valor nos lleva a tener que usar condicionales
            (grieta--gitea-issue "gitea.test.org" "demo" "grieta" 1)))))
   
(ert-deftest grieta-test-grieta--gitea-issue-check-url-path ()
  (with-mock
   ;; https://try.gitea.io/api/swagger#/issue/issueGetIssue
   (mock (grieta--gitea-call "gitea.test.org" "GET" "/api/v1/repos/demo/grieta/issues/99"))
   (grieta--gitea-issue "gitea.test.org" "demo" "grieta" 99)))

;; ACCEPTANCE
;; MANUAL
(provide 'grieta-tests)
```
```sh @mdtoapp @execute
make test
```
```text @output
Running 3 tests (2023-07-10 22:39:39-0500, selector ‘t’)
   passed  1/3  grieta-test-grieta--gitea-issue-check-url-path (0.000064 sec)
   passed  2/3  grieta-test-grieta--gitea-json->issues-on-invalid-data (0.000030 sec)
   passed  3/3  grieta-test-grieta--gitea-json->issues-on-valid-data (0.000067 sec)

Ran 3 tests, 3 results as expected, 0 unexpected (2023-07-10 22:39:39-0500, 0.000269 sec)
```

Ya con las pruebas en curso pasemos a completar la implementación es decir hacer la petición HTTP.

**grieta.el**
```lisp @mdtoapp @create file:grieta.el
(defun grieta--gitea-call (host method path)
  (let ((url-request-method method)
        (url-request-data path))
    (with-current-buffer
        (url-retrieve-synchronously (concat "https://" host path))
      (goto-char (point-min))
      (search-forward-regexp "\n\n")
      (buffer-substring (point) (point-max)))))

(defun grieta--gitea-issue (host owner repo issue-id)
  (let* ((response (grieta--gitea-call host "GET" (format "/api/v1/repos/%s/%s/issues/%d" owner repo issue-id))))
    (if response
        (let ((remote-issue (json-parse-string response)))
          (list (gethash "number" remote-issue)
                (gethash "title" remote-issue)))
      nil)))

;; https://www.gnu.org/software/emacs/manual/html_node/elisp/Named-Features.html
;; `provide` es necesario para luego importar con `require`
(provide 'grieta)
```

Verificamos manualmente que funcione la petición al servidor.

```sh @mdtoapp @execute
emacs --batch -l grieta.el --eval='(print (grieta--gitea-call "gitea.demo.org" "GET" "/api/v1/repos/bit4bit/prueba/issues/1"))'
```

```text @output
"{\"errors\":null,\"message\":\"The target couldn't be found.\",\"url\":\"https://gitea.onecluster.org/api/swagger\"}
"
```
uuum, consultemos a un repositorio público

```sh @mdtoapp @execute
emacs --batch -l grieta.el --eval='(print (grieta--gitea-call "gitea.onecluster.org" "GET" "/api/v1/repos/OneTeam/trytondo-analytic_operations/issues/1"))'

```

```text @output
"{\"id\":1846,\"url\":\"https://gitea.onecluster.org/api/v1/repos/OneTeam/trytondo-analytic_operations/issues/1\",\"html_url\":\"https://gitea.onecluster.org/OneTeam/trytondo-analytic_operations/issues/1\",\"number\":1,\"user\":{\"id\":5,\"login\":\"Rodia\",\"login_name\":\"\",\"full_name\":\"\",\"email\":\"rodia@noreply.onecluster.org\",\"avatar_url\":\"https://gitea.onecluster.org/avatar/2bb02a97b20d83fd00f4e7e044e9c1ef\",\"language\":\"\",\"is_admin\":false,\"last_login\":\"0001-01-01T00:00:00Z\",\"created\":\"2021-09-05T14:03:55-05:00\",\"restricted\":false,\"active\":false,\"prohibit_login\":false,\"location\":\"\",\"website\":\"\",\"description\":\"\",\"visibility\":\"public\",\"followers_count\":0,\"following_count\":0,\"starred_repos_count\":0,\"username\":\"Rodia\"},\"original_author\":\"\",\"original_author_id\":0,\"title\":\"AÃ±adir Centro de Costos en Encabezado a Facturas de Cliente y de Proveedor\",\"body\":\"\",\"ref\":\"\",\"assets\":[],\"labels\":[],\"milestone\":null,\"assignee\":null,\"assignees\":null,\"state\":\"open\",\"is_locked\":false,\"comments\":0,\"created_at\":\"2023-07-01T00:01:50-05:00\",\"updated_at\":\"2023-07-01T00:01:50-05:00\",\"closed_at\":null,\"due_date\":null,\"pull_request\":null,\"repository\":{\"id\":2134,\"name\":\"trytondo-analytic_operations\",\"owner\":\"OneTeam\",\"full_name\":\"OneTeam/trytondo-analytic_operations\"}}
"
```
ajaa, investigando un poco sobre la API nos indica que para repositorios privados
debemos que una Api Key, y ya ilustrados sobre este escenario procedamos a implementarlo,
pero antes revisemos algunos caminos.

1. crear otra función ejemplo `grieta--gitea-auth-call`.
2. usar una variable global o función que nos entregue el api-key.
3. adicionar otro parámetro a la función `grieta-gitea-call`.

La primera ya que aunque puede ser la más "sencilla" tendríamos
redundancia en la lógica que a posterior nos afectaría el mantenimiento, entonces la descartamos.

Ahora valoremos la segunda, especulemos sobre una posible implementación

```lisp
(defvar grieta-gitea-api-key-call nil)

(defun grieta--gitea-call (host method path)
  (let ((url-request-method method)
        ((url-request-data path))
        
        (if grieta-gitea-api-key-call
            (url-request-extra-headers
             ;; acoplamiento
             (cons "Authorization"  (cons "token " grieta-gitea-api-key-call)))))

  (with-current-buffer
      (url-retrieve-synchronously (concat "https://" host path))
    (goto-char (point-min))
    (search-forward-regexp "\n\n")
    (buffer-substring (point) (point-max)))))
```
al ejecutar las pruebas estas pasan, pero miremos algo, recordando lo siguiente:

* `UNIT` funciones que solo varían su comportamiento según los argumentos.
* `INTEGRATION` funciones que varían su comportamiento según los argumentos u otros medios como variables globales o lecturas fuera del proceso (lectura disco, red, etc..).
* `ACCEPTANCE` los mismo criterios que `INTEGRATION` pero solo sobre las funciones publicadas es decir sobre las funciones que son accesible por el usuario u otros paquetes.
* `MANUAL` pruebas que se deben realizar manualmente.

hasta ahora hemos realizado las pruebas en el grupo de `INTEGRATION` donde
hemos verificado la interacción con `grieta--gitea-call` por medio de un doble,
hasta ahora no nos hemos interesado en probar la función ya que esta es una pasarela un servicio
externo donde no tenemos mas lógica que la peticion al servicio pero ahora
la situación cambia ya que creamos una dependencia implícita (ya que solo leyendo la implementación podemos determinarla) a una variable que va ser
modificada por el usuario creando un acoplamiento de un mecanismo privado a un
mecanismo netamente publico, acá es de suma importancia mantener una clara separacion
entre capas, `grieta--gitea-call` pertenece a una capa privada
mientras que `grieta-gitea-api-key-call` pertenece a una capa publica,
por lo tanto necesitaríamos algún mecanismo que nos permita pasar la configuración
desde la capa publica a la privada.
ahora especulemos sobre adicionar un parámetro

**grieta.el**
```lisp @mdtoapp @create file:grieta.el
(defun grieta--gitea-call (host api-key method path)
  (let ((url-request-method method)
        (url-request-data path)
        (url-request-extra-headers
         (list (cons "Authorization"  (concat "token " api-key)))))
        
        (with-current-buffer
            (url-retrieve-synchronously (concat "https://" host path))
          (goto-char (point-min))
          (search-forward-regexp "\n\n")
          (buffer-substring (point) (point-max)))))

(defun grieta--gitea-issue (host owner repo issue-id)
  (let* ((response (grieta--gitea-call host "GET" (format "/api/v1/repos/%s/%s/issues/%d" owner repo issue-id))))
    (if response
        (let ((remote-issue (json-parse-string response)))
          (list (gethash "number" remote-issue)
                (gethash "title" remote-issue)))
      nil)))

;; https://www.gnu.org/software/emacs/manual/html_node/elisp/Named-Features.html
;; `provide` es necesario para luego importar con `require`
(provide 'grieta)
```

al cambiar la firma de la función debemos actualizar todas las funciones dependientes [Martin Fowler en Refactoring](https://refactoring.com/)
propone una metodología estructura de realizar esto, ya que es algo que estamos haciendo constantemente y es de suma importancia aprender a
refactorizar para no tomar caminos mas "rápidos" que a largo plazo son perjudiciales, ahora si valoramos nuevamente donde 
realizaríamos las pruebas estas irían en `INTEGRATION` ya que realizan lecturas
fuera de proceso, y mirando un poco mas de cerca la función esta solo realiza actividades fuera de proceso osea
que si realizáramos pruebas tendríamos que usar dobles para simular el comportamiento de funciones como `url-retrieve-synchronously`
pero este tipo de pruebas seria falsas ya que no conocemos o participamos del ciclo de desarrollo de las funciones, 
por lo cual este tipo de funciones las verificaremos indirectamente en comportamientos de `INTEGRATION` o `ACCEPTANCE` o bien `MANUAL`.

```sh @mdtoapp @execute
emacs --batch -l grieta.el --eval='(print (grieta--gitea-call "gitea.demo.org" "oeuaeiaoi" "GET" "/api/v1/repos/bit4bit/prueba/issues/1"))'
```
```text @output
"{\"id\":1861,\"url\":\"https://gitea.onecluster.org/api/v1/repos/bit4bit/prueba/issues/1\",\"html_url\":\"https://gitea.onecluster.org/bit4bit/prueba/issues/1\",\"number\":1,\"user\":{\"id\":10,\"login\":\"bit4bit\",\"login_name\":\"\",\"full_name\":\"\",\"email\":\"bit4bit@noreply.onecluster.org\",\"avatar_url\":\"https://gitea.onecluster.org/avatar/5984dea48f59d0cbf32c81e80cc81525\",\"language\":\"\",\"is_admin\":false,\"last_login\":\"0001-01-01T00:00:00Z\",\"created\":\"2021-11-24T14:59:21-05:00\",\"restricted\":false,\"active\":false,\"prohibit_login\":false,\"location\":\"\",\"website\":\"\",\"description\":\"\",\"visibility\":\"private\",\"followers_count\":0,\"following_count\":0,\"starred_repos_count\":0,\"username\":\"bit4bit\"},\"original_author\":\"\",\"original_author_id\":0,\"title\":\"prueba\",\"body\":\"\",\"ref\":\"\",\"assets\":[],\"labels\":[],\"milestone\":null,\"assignee\":null,\"assignees\":null,\"state\":\"open\",\"is_locked\":false,\"comments\":0,\"created_at\":\"2023-07-07T13:57:25-05:00\",\"updated_at\":\"2023-07-08T10:07:39-05:00\",\"closed_at\":null,\"due_date\":null,\"pull_request\":null,\"repository\":{\"id\":2098,\"name\":\"prueba\",\"owner\":\"bit4bit\",\"full_name\":\"bit4bit/prueba\"}}
"
```

```sh @mdtoapp @execute
make test
```
```text @output
Running 3 tests (2023-07-14 20:36:23-0500, selector ‘t’)
   passed  1/3  grieta-test-grieta--gitea-issue-check-url-path (0.000058 sec)
   passed  2/3  grieta-test-grieta--gitea-json->issues-on-invalid-data (0.000029 sec)
   passed  3/3  grieta-test-grieta--gitea-json->issues-on-valid-data (0.000061 sec)

Ran 3 tests, 3 results as expected, 0 unexpected (2023-07-14 20:36:23-0500, 0.000264 sec)
```

aaa pero que paso?? porque pasaron las pruebas si cambio la firma de la función?? esto es algo que
suele ocurre cuando usamos dobles, por algo se le llama dobles no es el elemento de origen, esto puede aclarar un poco de porque se recomienda evitar los dobles sobre elementos que no sean implementados por el proyecto,

por ejemplo `url-request-method` tiene un ciclo de desarrollo diferente.

ahora miremos un poco como podemos mitigar el efecto de los dobles

**grieta.el**
```lisp @mdtoapp @create file:grieta.el
(defun grieta--gitea-call (host api-key method path)
  (let ((url-request-method method)
        (url-request-data path)
        (url-request-extra-headers
         (list (cons "Authorization"  (concat "token " api-key)))))
        
        (with-current-buffer
            (url-retrieve-synchronously (concat "https://" host path))
          (goto-char (point-min))
          (search-forward-regexp "\n\n")
          (buffer-substring (point) (point-max)))))

(defun grieta--gitea-issue (host owner repo issue-id)
  ;; es una abstraccion sobre la comunicacion con gitea
  (let* ((response (grieta--gitea-call host "GET" (format "/api/v1/repos/%s/%s/issues/%d" owner repo issue-id))))
    (if response
        ;; pero aca tenemos un mecanismo de implementacion
        (let ((remote-issue (json-parse-string response)))

          ;; nuevamente una abstraccion
          (list (gethash "number" remote-issue)
                (gethash "title" remote-issue)))
      nil)))

;; https://www.gnu.org/software/emacs/manual/html_node/elisp/Named-Features.html
;; `provide` es necesario para luego importar con `require`
(provide 'grieta)
```

antes que nada no debemos forzar los conceptos, si algo no cuadra es porque no va, revisando
nuevamente la función `grieta--gitea-issue` se puede ver que ahí un brinco entre diferentes niveles de abstracción (`grieta-gitea-call` `json-parse-string`, `gethash`),
vamos a llevar esto a un solo nivel.

**grieta.el**
```lisp @mdtoapp @create file:grieta.el
(defun grieta--url-auth-request (method token path)
  (let ((url-request-method method)
        (url-request-data path)
        (url-request-extra-headers
         (list (cons "Authorization"  (concat "token " token)))))
        
        (with-current-buffer
            (url-retrieve-synchronously path)
          (goto-char (point-min))
          (search-forward-regexp "\n\n")
          (buffer-substring (point) (point-max)))))

(defun grieta--gitea-call (host api-key method path)
  (let ((response (grieta--url-auth-request method api-key (concat "https://" host path))))
    (if response
        (json-parse-string response)
      nil)))

(defun grieta--gitea-issue (host owner repo issue-id)
  (let* ((remote-issue (grieta--gitea-call host "GET" (format "/api/v1/repos/%s/%s/issues/%d" owner repo issue-id))))
    (if remote-issue
        (list (gethash "number" remote-issue)
              (gethash "title" remote-issue))
    nil)))

;; https://www.gnu.org/software/emacs/manual/html_node/elisp/Named-Features.html
;; `provide` es necesario para luego importar con `require`
(provide 'grieta)
```

la mitigación se da aislando en lo posible la peticion HTTP `grieta—url-auth-request`. Hemos aplanado los niveles de abstracción; por ejemplo `grieta--gitea-issue` habla en términos de una petición
y la traducción a estructura interna, mientras que `grieta--gitea-call` habla en términos de petición HTTP.
Como movimos las responsabilidades debemos actualizar los dobles usados en las pruebas.

**tests/grieta-tests.el**
```lisp @mdtoapp @create file:tests/grieta-tests.el
(require 'el-mock)
(require 'grieta)

;; UNIT
;; INTEGRATION
(ert-deftest grieta-test-grieta--gitea-json->issues-on-valid-data ()
  (with-mock
   ;; https://try.gitea.io/api/swagger#/issue/issueGetIssue
   ;; respuesta simplificada
   (stub grieta--url-auth-request => "{\"id\": 1000, \"number\": 1, \"title\": \"Test\"}")

   (should (equal
            '(1 "Test")
            (grieta--gitea-issue "gitea.test.org" "demo" "grieta" 1)))))

(ert-deftest grieta-test-grieta--gitea-json->issues-on-invalid-data ()
  (with-mock
   ;; https://try.gitea.io/api/swagger#/issue/issueGetIssue
   (stub grieta--url-auth-request => nil)

   (should (equal
            nil ; volveremos a revisar luego si esta valor nos lleva a tener que usar condicionales
            (grieta--gitea-issue "gitea.test.org" "demo" "grieta" 1)))))
   
(ert-deftest grieta-test-grieta--gitea-issue-check-url-path ()
  (with-mock
   ;; https://try.gitea.io/api/swagger#/issue/issueGetIssue
   (mock (grieta--url-auth-request "gitea.test.org" "GET" "/api/v1/repos/demo/grieta/issues/99"))
   (grieta--gitea-issue "gitea.test.org" "demo" "grieta" 99)))

;; ACCEPTANCE
;; MANUAL
(provide 'grieta-tests)
```
```sh @mdtoapp @execute
make test
```
```text @output
Test grieta-test-grieta--gitea-json->issues-on-valid-data condition:
    (wrong-number-of-arguments
     (lambda
       (host api-key method path)
       (let
	   ((response ...))
	 (json-parse-string response)))
     3)
   FAILED  3/3  grieta-test-grieta--gitea-json->issues-on-valid-data (0.000073 sec)

Ran 3 tests, 0 results as expected, 3 unexpected (2023-07-14 21:01:44-0500, 0.114529 sec)

3 unexpected results:
   FAILED  grieta-test-grieta--gitea-issue-check-url-path
   FAILED  grieta-test-grieta--gitea-json->issues-on-invalid-data
   FAILED  grieta-test-grieta--gitea-json->issues-on-valid-data
```

Excelente ya se nos manifestó el error, procedamos a actualizar.

**grieta.el**
```lisp @mdtoapp @create file:grieta.el
(defun grieta--url-auth-request (method token path)
  (let ((url-request-method method)
        (url-request-data path)
        (url-request-extra-headers
         (list (cons "Authorization"  (concat "token " token)))))
        
        (with-current-buffer
            (url-retrieve-synchronously path)
          (goto-char (point-min))
          (search-forward-regexp "\n\n")
          (buffer-substring (point) (point-max)))))

(defun grieta--gitea-call (host api-key method path)
  (let ((response (grieta--url-auth-request method api-key (concat "https://" host path))))
    (if response
        (json-parse-string response)
      nil)))

(defun grieta--gitea-issue (host owner repo issue-id)
  ;; TODO: api-key?
  (let* ((remote-issue (grieta--gitea-call host nil "GET" (format "/api/v1/repos/%s/%s/issues/%d" owner repo issue-id))))
    (if remote-issue
        (list (gethash "number" remote-issue)
              (gethash "title" remote-issue))
      nil)))

;; https://www.gnu.org/software/emacs/manual/html_node/elisp/Named-Features.html
;; `provide` es necesario para luego importar con `require`
(provide 'grieta)
```

```sh @mdtoapp @execute
make test
```
```text @output
Test grieta-test-grieta--gitea-issue-check-url-path condition:
    (mock-error
     (grieta--url-auth-request "gitea.test.org" "GET" "/api/v1/repos/demo/grieta/issues/99")
     (grieta--url-auth-request "GET" nil "https://gitea.test.org/api/v1/repos/demo/grieta/issues/99"))
   FAILED  1/3  grieta-test-grieta--gitea-issue-check-url-path (0.000096 sec)
   passed  2/3  grieta-test-grieta--gitea-json->issues-on-invalid-data (0.000044 sec)
   passed  3/3  grieta-test-grieta--gitea-json->issues-on-valid-data (0.000082 sec)

Ran 3 tests, 2 results as expected, 1 unexpected (2023-07-14 21:11:49-0500, 0.040699 sec)
```
excelente nos estamos ilustrando.

**tests/grieta-tests.el**
```lisp @mdtoapp @create file:tests/grieta-tests.el
(require 'el-mock)
(require 'grieta)

;; UNIT
;; INTEGRATION
(ert-deftest grieta-test-grieta--gitea-json->issues-on-valid-data ()
  (with-mock
   ;; https://try.gitea.io/api/swagger#/issue/issueGetIssue
   ;; respuesta simplificada
   (stub grieta--url-auth-request => "{\"id\": 1000, \"number\": 1, \"title\": \"Test\"}")

   (should (equal
            '(1 "Test")
            (grieta--gitea-issue "gitea.test.org" "demo" "grieta" 1)))))

(ert-deftest grieta-test-grieta--gitea-json->issues-on-invalid-data ()
  (with-mock
   ;; https://try.gitea.io/api/swagger#/issue/issueGetIssue
   (stub grieta--url-auth-request => nil)

   (should (equal
            nil ; volveremos a revisar luego si esta valor nos lleva a tener que usar condicionales
            (grieta--gitea-issue "gitea.test.org" "demo" "grieta" 1)))))
   
(ert-deftest grieta-test-grieta--gitea-issue-check-url-path ()
  (with-mock
   ;; https://try.gitea.io/api/swagger#/issue/issueGetIssue
   (mock (grieta--url-auth-request "GET" "token" "https://gitea.test.org/api/v1/repos/demo/grieta/issues/99"))
   (grieta--gitea-issue "gitea.test.org" "demo" "grieta" 99)))

;; ACCEPTANCE
;; MANUAL
(provide 'grieta-tests)
```
```sh @mdtoapp @execute
make test
```
```text @output
Test grieta-test-grieta--gitea-issue-check-url-path condition:
    (mock-error
     (grieta--url-auth-request "GET" "token" "/api/v1/repos/demo/grieta/issues/99")
     (grieta--url-auth-request "GET" nil "https://gitea.test.org/api/v1/repos/demo/grieta/issues/99"))
   FAILED  1/3  grieta-test-grieta--gitea-issue-check-url-path (0.000098 sec)
   passed  2/3  grieta-test-grieta--gitea-json->issues-on-invalid-data (0.000043 sec)
   passed  3/3  grieta-test-grieta--gitea-json->issues-on-valid-data (0.000080 sec)

Ran 3 tests, 2 results as expected, 1 unexpected (2023-07-14 21:14:39-0500, 0.041251 sec)
```


las pruebas nos esta ilustrando, diciéndonos que piensa hacer con el token?
nuevamente tenemos varios caminos

1. crear otra función ejemplo `grieta--gitea-auth-issue`
2. usar una variable global o función que nos entregue el api-key.
3. adicionar otro parámetro a la función `grieta--gitea-issue`.

ya conocemos las implicaciones de las dos primeras, vayámonos directamente
por la tercera.

`invito hacer el ejerció de llegar al estado presentado desde al archivo anteriormente expuesto a este, haciendo cambios que no dejen fallar las pruebas`
**grieta.el**
```lisp @mdtoapp @create file:grieta.el
(defun grieta--url-auth-request (method token path)
  (let ((url-request-method method)
        (url-request-data path)
        (url-request-extra-headers
         (list (cons "Authorization"  (concat "token " token)))))
        
        (with-current-buffer
            (url-retrieve-synchronously path)
          (goto-char (point-min))
          (search-forward-regexp "\n\n")
          (buffer-substring (point) (point-max)))))

(defun grieta--gitea-call (host api-key method path)
  (let ((response (grieta--url-auth-request method api-key (concat "https://" host path))))
    (if response
        (json-parse-string response)
      nil)))

(defun grieta--gitea-issue (host api-key owner repo issue-id)
  ;; TODO: api-key?
  (let* ((remote-issue (grieta--gitea-call host api-key "GET" (format "/api/v1/repos/%s/%s/issues/%d" owner repo issue-id))))
    (if remote-issue
        (list (gethash "number" remote-issue)
              (gethash "title" remote-issue))
      nil)))

;; https://www.gnu.org/software/emacs/manual/html_node/elisp/Named-Features.html
;; `provide` es necesario para luego importar con `require`
(provide 'grieta)
```

**tests/grieta-tests.el**
```lisp @mdtoapp @create file:tests/grieta-tests.el
(require 'el-mock)
(require 'grieta)

;; UNIT
;; INTEGRATION
(ert-deftest grieta-test-grieta--gitea-json->issues-on-valid-data ()
  (with-mock
   ;; https://try.gitea.io/api/swagger#/issue/issueGetIssue
   ;; respuesta simplificada
   (stub grieta--url-auth-request => "{\"id\": 1000, \"number\": 1, \"title\": \"Test\"}")

   (should (equal
            '(1 "Test")
            (grieta--gitea-issue "gitea.test.org" "token" "demo" "grieta" 1)))))

(ert-deftest grieta-test-grieta--gitea-json->issues-on-invalid-data ()
  (with-mock
   ;; https://try.gitea.io/api/swagger#/issue/issueGetIssue
   (stub grieta--url-auth-request => nil)

   (should (equal
            nil ; volveremos a revisar luego si esta valor nos lleva a tener que usar condicionales
            (grieta--gitea-issue "gitea.test.org" "token" "demo" "grieta" 1)))))
   
(ert-deftest grieta-test-grieta--gitea-issue-check-url-path ()
  (with-mock
   ;; https://try.gitea.io/api/swagger#/issue/issueGetIssue
   (mock (grieta--url-auth-request "GET" "token" "https://gitea.test.org/api/v1/repos/demo/grieta/issues/99"))
   (grieta--gitea-issue "gitea.test.org" "token" "demo" "grieta" 99)))

;; ACCEPTANCE
;; MANUAL
(provide 'grieta-tests)
```
```sh @mdtoapp @execute
make test
```
```text @output
Running 3 tests (2023-07-14 21:19:48-0500, selector ‘t’)
   passed  1/3  grieta-test-grieta--gitea-issue-check-url-path (0.000064 sec)
   passed  2/3  grieta-test-grieta--gitea-json->issues-on-invalid-data (0.000033 sec)
   passed  3/3  grieta-test-grieta--gitea-json->issues-on-valid-data (0.000069 sec)

Ran 3 tests, 3 results as expected, 0 unexpected (2023-07-14 21:19:48-0500, 0.000305 sec)
```

boom, nuevamente estables, el proceso que realizamos de:

1. especular
2. ilustrar
3. ajustar (+ importante no olvidar limpiar y aclarar lo escrito)

se realiza rápidamente en cuestión de segundos.
