Navegador
fix: tighten framework detection — query Import nodes, not substring matches detect() now checks Import nodes by exact module name (n.name = $name OR n.module = $name) instead of CONTAINS on all nodes. Added detection_files for marker file matching by exact File node name. Eliminates false positives: navegador's own codebase now correctly reports zero framework detections instead of falsely matching FastAPI and Laravel. Bumps to 0.7.4.
Commit
2c266d225208a3e8cfc6bed8226530ffbdd04a65ce7768011cbe08655720bff5
Parent
04dabf9624f861f…
20 files changed
+1
-1
+30
-7
+5
-1
+1
-1
+1
-1
+5
-1
+5
-1
+5
-1
+5
-1
+5
-1
+1
-1
+2
-4
+9
-7
+4
-8
+12
-8
+8
-8
+14
-14
+12
-11
+4
-4
+15
-15
~
navegador/__init__.py
~
navegador/enrichment/base.py
~
navegador/enrichment/django.py
~
navegador/enrichment/express.py
~
navegador/enrichment/fastapi.py
~
navegador/enrichment/laravel.py
~
navegador/enrichment/rails.py
~
navegador/enrichment/react.py
~
navegador/enrichment/react_native.py
~
navegador/enrichment/spring.py
~
pyproject.toml
~
tests/test_enrichment_base.py
~
tests/test_enrichment_django.py
~
tests/test_enrichment_express.py
~
tests/test_enrichment_fastapi.py
~
tests/test_enrichment_laravel.py
~
tests/test_enrichment_rails.py
~
tests/test_enrichment_react.py
~
tests/test_enrichment_react_native.py
~
tests/test_enrichment_spring.py
+1
-1
| --- navegador/__init__.py | ||
| +++ navegador/__init__.py | ||
| @@ -1,10 +1,10 @@ | ||
| 1 | 1 | """ |
| 2 | 2 | Navegador — AST + knowledge graph context engine for AI coding agents. |
| 3 | 3 | """ |
| 4 | 4 | |
| 5 | -__version__ = "0.7.3" | |
| 5 | +__version__ = "0.7.4" | |
| 6 | 6 | __author__ = "CONFLICT LLC" |
| 7 | 7 | |
| 8 | 8 | from navegador.sdk import Navegador |
| 9 | 9 | |
| 10 | 10 | __all__ = ["Navegador"] |
| 11 | 11 |
| --- navegador/__init__.py | |
| +++ navegador/__init__.py | |
| @@ -1,10 +1,10 @@ | |
| 1 | """ |
| 2 | Navegador — AST + knowledge graph context engine for AI coding agents. |
| 3 | """ |
| 4 | |
| 5 | __version__ = "0.7.3" |
| 6 | __author__ = "CONFLICT LLC" |
| 7 | |
| 8 | from navegador.sdk import Navegador |
| 9 | |
| 10 | __all__ = ["Navegador"] |
| 11 |
| --- navegador/__init__.py | |
| +++ navegador/__init__.py | |
| @@ -1,10 +1,10 @@ | |
| 1 | """ |
| 2 | Navegador — AST + knowledge graph context engine for AI coding agents. |
| 3 | """ |
| 4 | |
| 5 | __version__ = "0.7.4" |
| 6 | __author__ = "CONFLICT LLC" |
| 7 | |
| 8 | from navegador.sdk import Navegador |
| 9 | |
| 10 | __all__ = ["Navegador"] |
| 11 |
+30
-7
| --- navegador/enrichment/base.py | ||
| +++ navegador/enrichment/base.py | ||
| @@ -32,31 +32,54 @@ | ||
| 32 | 32 | """Name of the framework (e.g. 'django', 'fastapi').""" |
| 33 | 33 | |
| 34 | 34 | @property |
| 35 | 35 | @abstractmethod |
| 36 | 36 | def detection_patterns(self) -> list[str]: |
| 37 | - """File/import patterns that indicate this framework is in use. | |
| 37 | + """Import module names that indicate this framework is in use. | |
| 38 | + | |
| 39 | + Detection queries Import nodes for exact module matches, so use | |
| 40 | + the actual package name (e.g. 'django', 'fastapi', 'express'). | |
| 41 | + """ | |
| 42 | + | |
| 43 | + @property | |
| 44 | + def detection_files(self) -> list[str]: | |
| 45 | + """Filenames whose presence confirms the framework. | |
| 38 | 46 | |
| 39 | - E.g. ['manage.py', 'django.conf.settings'] for Django. | |
| 47 | + Override this to add file-based detection (e.g. 'manage.py' for | |
| 48 | + Django, 'Gemfile' for Rails). File names are matched exactly | |
| 49 | + against File node names — not as substrings. | |
| 40 | 50 | """ |
| 51 | + return [] | |
| 41 | 52 | |
| 42 | 53 | @abstractmethod |
| 43 | 54 | def enrich(self) -> EnrichmentResult: |
| 44 | 55 | """Run enrichment on the current graph.""" |
| 45 | 56 | |
| 46 | 57 | def detect(self) -> bool: |
| 47 | - """Check if the framework is present in the graph by looking for detection patterns.""" | |
| 58 | + """Check if the framework is present by looking for real imports and marker files.""" | |
| 59 | + # Check Import nodes for actual framework imports | |
| 48 | 60 | for pattern in self.detection_patterns: |
| 49 | 61 | result = self.store.query( |
| 50 | - "MATCH (n) WHERE n.name CONTAINS $pattern OR " | |
| 51 | - "(n.file_path IS NOT NULL AND n.file_path CONTAINS $pattern) " | |
| 52 | - "RETURN count(n) AS c LIMIT 1", | |
| 53 | - {"pattern": pattern}, | |
| 62 | + "MATCH (n:Import) WHERE n.name = $name OR n.module = $name " | |
| 63 | + "RETURN count(n) AS c", | |
| 64 | + {"name": pattern}, | |
| 65 | + ) | |
| 66 | + rows = result.result_set or [] | |
| 67 | + if rows and rows[0][0] > 0: | |
| 68 | + return True | |
| 69 | + | |
| 70 | + # Check for marker files by exact filename match | |
| 71 | + for filename in self.detection_files: | |
| 72 | + result = self.store.query( | |
| 73 | + "MATCH (f:File) WHERE f.name = $name " | |
| 74 | + "RETURN count(f) AS c", | |
| 75 | + {"name": filename}, | |
| 54 | 76 | ) |
| 55 | 77 | rows = result.result_set or [] |
| 56 | 78 | if rows and rows[0][0] > 0: |
| 57 | 79 | return True |
| 80 | + | |
| 58 | 81 | return False |
| 59 | 82 | |
| 60 | 83 | def _promote_node( |
| 61 | 84 | self, name: str, file_path: str, semantic_type: str, props: dict = None |
| 62 | 85 | ) -> None: |
| 63 | 86 |
| --- navegador/enrichment/base.py | |
| +++ navegador/enrichment/base.py | |
| @@ -32,31 +32,54 @@ | |
| 32 | """Name of the framework (e.g. 'django', 'fastapi').""" |
| 33 | |
| 34 | @property |
| 35 | @abstractmethod |
| 36 | def detection_patterns(self) -> list[str]: |
| 37 | """File/import patterns that indicate this framework is in use. |
| 38 | |
| 39 | E.g. ['manage.py', 'django.conf.settings'] for Django. |
| 40 | """ |
| 41 | |
| 42 | @abstractmethod |
| 43 | def enrich(self) -> EnrichmentResult: |
| 44 | """Run enrichment on the current graph.""" |
| 45 | |
| 46 | def detect(self) -> bool: |
| 47 | """Check if the framework is present in the graph by looking for detection patterns.""" |
| 48 | for pattern in self.detection_patterns: |
| 49 | result = self.store.query( |
| 50 | "MATCH (n) WHERE n.name CONTAINS $pattern OR " |
| 51 | "(n.file_path IS NOT NULL AND n.file_path CONTAINS $pattern) " |
| 52 | "RETURN count(n) AS c LIMIT 1", |
| 53 | {"pattern": pattern}, |
| 54 | ) |
| 55 | rows = result.result_set or [] |
| 56 | if rows and rows[0][0] > 0: |
| 57 | return True |
| 58 | return False |
| 59 | |
| 60 | def _promote_node( |
| 61 | self, name: str, file_path: str, semantic_type: str, props: dict = None |
| 62 | ) -> None: |
| 63 |
| --- navegador/enrichment/base.py | |
| +++ navegador/enrichment/base.py | |
| @@ -32,31 +32,54 @@ | |
| 32 | """Name of the framework (e.g. 'django', 'fastapi').""" |
| 33 | |
| 34 | @property |
| 35 | @abstractmethod |
| 36 | def detection_patterns(self) -> list[str]: |
| 37 | """Import module names that indicate this framework is in use. |
| 38 | |
| 39 | Detection queries Import nodes for exact module matches, so use |
| 40 | the actual package name (e.g. 'django', 'fastapi', 'express'). |
| 41 | """ |
| 42 | |
| 43 | @property |
| 44 | def detection_files(self) -> list[str]: |
| 45 | """Filenames whose presence confirms the framework. |
| 46 | |
| 47 | Override this to add file-based detection (e.g. 'manage.py' for |
| 48 | Django, 'Gemfile' for Rails). File names are matched exactly |
| 49 | against File node names — not as substrings. |
| 50 | """ |
| 51 | return [] |
| 52 | |
| 53 | @abstractmethod |
| 54 | def enrich(self) -> EnrichmentResult: |
| 55 | """Run enrichment on the current graph.""" |
| 56 | |
| 57 | def detect(self) -> bool: |
| 58 | """Check if the framework is present by looking for real imports and marker files.""" |
| 59 | # Check Import nodes for actual framework imports |
| 60 | for pattern in self.detection_patterns: |
| 61 | result = self.store.query( |
| 62 | "MATCH (n:Import) WHERE n.name = $name OR n.module = $name " |
| 63 | "RETURN count(n) AS c", |
| 64 | {"name": pattern}, |
| 65 | ) |
| 66 | rows = result.result_set or [] |
| 67 | if rows and rows[0][0] > 0: |
| 68 | return True |
| 69 | |
| 70 | # Check for marker files by exact filename match |
| 71 | for filename in self.detection_files: |
| 72 | result = self.store.query( |
| 73 | "MATCH (f:File) WHERE f.name = $name " |
| 74 | "RETURN count(f) AS c", |
| 75 | {"name": filename}, |
| 76 | ) |
| 77 | rows = result.result_set or [] |
| 78 | if rows and rows[0][0] > 0: |
| 79 | return True |
| 80 | |
| 81 | return False |
| 82 | |
| 83 | def _promote_node( |
| 84 | self, name: str, file_path: str, semantic_type: str, props: dict = None |
| 85 | ) -> None: |
| 86 |
+5
-1
| --- navegador/enrichment/django.py | ||
| +++ navegador/enrichment/django.py | ||
| @@ -20,11 +20,15 @@ | ||
| 20 | 20 | def framework_name(self) -> str: |
| 21 | 21 | return "django" |
| 22 | 22 | |
| 23 | 23 | @property |
| 24 | 24 | def detection_patterns(self) -> list[str]: |
| 25 | - return ["manage.py", "django.conf", "settings.py", "urls.py"] | |
| 25 | + return ["django", "django.conf", "django.db", "django.http"] | |
| 26 | + | |
| 27 | + @property | |
| 28 | + def detection_files(self) -> list[str]: | |
| 29 | + return ["manage.py"] | |
| 26 | 30 | |
| 27 | 31 | def enrich(self) -> EnrichmentResult: |
| 28 | 32 | result = EnrichmentResult() |
| 29 | 33 | |
| 30 | 34 | # ── Views ──────────────────────────────────────────────────────────── |
| 31 | 35 |
| --- navegador/enrichment/django.py | |
| +++ navegador/enrichment/django.py | |
| @@ -20,11 +20,15 @@ | |
| 20 | def framework_name(self) -> str: |
| 21 | return "django" |
| 22 | |
| 23 | @property |
| 24 | def detection_patterns(self) -> list[str]: |
| 25 | return ["manage.py", "django.conf", "settings.py", "urls.py"] |
| 26 | |
| 27 | def enrich(self) -> EnrichmentResult: |
| 28 | result = EnrichmentResult() |
| 29 | |
| 30 | # ── Views ──────────────────────────────────────────────────────────── |
| 31 |
| --- navegador/enrichment/django.py | |
| +++ navegador/enrichment/django.py | |
| @@ -20,11 +20,15 @@ | |
| 20 | def framework_name(self) -> str: |
| 21 | return "django" |
| 22 | |
| 23 | @property |
| 24 | def detection_patterns(self) -> list[str]: |
| 25 | return ["django", "django.conf", "django.db", "django.http"] |
| 26 | |
| 27 | @property |
| 28 | def detection_files(self) -> list[str]: |
| 29 | return ["manage.py"] |
| 30 | |
| 31 | def enrich(self) -> EnrichmentResult: |
| 32 | result = EnrichmentResult() |
| 33 | |
| 34 | # ── Views ──────────────────────────────────────────────────────────── |
| 35 |
+1
-1
| --- navegador/enrichment/express.py | ||
| +++ navegador/enrichment/express.py | ||
| @@ -32,11 +32,11 @@ | ||
| 32 | 32 | def framework_name(self) -> str: |
| 33 | 33 | return "express" |
| 34 | 34 | |
| 35 | 35 | @property |
| 36 | 36 | def detection_patterns(self) -> list[str]: |
| 37 | - return ["express", "Express", "app.listen"] | |
| 37 | + return ["express"] | |
| 38 | 38 | |
| 39 | 39 | def enrich(self) -> EnrichmentResult: |
| 40 | 40 | result = EnrichmentResult() |
| 41 | 41 | |
| 42 | 42 | # ── Routes: app.<method> or router.<method> patterns ───────────────── |
| 43 | 43 |
| --- navegador/enrichment/express.py | |
| +++ navegador/enrichment/express.py | |
| @@ -32,11 +32,11 @@ | |
| 32 | def framework_name(self) -> str: |
| 33 | return "express" |
| 34 | |
| 35 | @property |
| 36 | def detection_patterns(self) -> list[str]: |
| 37 | return ["express", "Express", "app.listen"] |
| 38 | |
| 39 | def enrich(self) -> EnrichmentResult: |
| 40 | result = EnrichmentResult() |
| 41 | |
| 42 | # ── Routes: app.<method> or router.<method> patterns ───────────────── |
| 43 |
| --- navegador/enrichment/express.py | |
| +++ navegador/enrichment/express.py | |
| @@ -32,11 +32,11 @@ | |
| 32 | def framework_name(self) -> str: |
| 33 | return "express" |
| 34 | |
| 35 | @property |
| 36 | def detection_patterns(self) -> list[str]: |
| 37 | return ["express"] |
| 38 | |
| 39 | def enrich(self) -> EnrichmentResult: |
| 40 | result = EnrichmentResult() |
| 41 | |
| 42 | # ── Routes: app.<method> or router.<method> patterns ───────────────── |
| 43 |
+1
-1
| --- navegador/enrichment/fastapi.py | ||
| +++ navegador/enrichment/fastapi.py | ||
| @@ -27,11 +27,11 @@ | ||
| 27 | 27 | def framework_name(self) -> str: |
| 28 | 28 | return "fastapi" |
| 29 | 29 | |
| 30 | 30 | @property |
| 31 | 31 | def detection_patterns(self) -> list[str]: |
| 32 | - return ["fastapi", "FastAPI", "APIRouter"] | |
| 32 | + return ["fastapi"] | |
| 33 | 33 | |
| 34 | 34 | # ── Enrichment ──────────────────────────────────────────────────────────── |
| 35 | 35 | |
| 36 | 36 | def enrich(self) -> EnrichmentResult: |
| 37 | 37 | result = EnrichmentResult() |
| 38 | 38 |
| --- navegador/enrichment/fastapi.py | |
| +++ navegador/enrichment/fastapi.py | |
| @@ -27,11 +27,11 @@ | |
| 27 | def framework_name(self) -> str: |
| 28 | return "fastapi" |
| 29 | |
| 30 | @property |
| 31 | def detection_patterns(self) -> list[str]: |
| 32 | return ["fastapi", "FastAPI", "APIRouter"] |
| 33 | |
| 34 | # ── Enrichment ──────────────────────────────────────────────────────────── |
| 35 | |
| 36 | def enrich(self) -> EnrichmentResult: |
| 37 | result = EnrichmentResult() |
| 38 |
| --- navegador/enrichment/fastapi.py | |
| +++ navegador/enrichment/fastapi.py | |
| @@ -27,11 +27,11 @@ | |
| 27 | def framework_name(self) -> str: |
| 28 | return "fastapi" |
| 29 | |
| 30 | @property |
| 31 | def detection_patterns(self) -> list[str]: |
| 32 | return ["fastapi"] |
| 33 | |
| 34 | # ── Enrichment ──────────────────────────────────────────────────────────── |
| 35 | |
| 36 | def enrich(self) -> EnrichmentResult: |
| 37 | result = EnrichmentResult() |
| 38 |
+5
-1
| --- navegador/enrichment/laravel.py | ||
| +++ navegador/enrichment/laravel.py | ||
| @@ -19,11 +19,15 @@ | ||
| 19 | 19 | def framework_name(self) -> str: |
| 20 | 20 | return "laravel" |
| 21 | 21 | |
| 22 | 22 | @property |
| 23 | 23 | def detection_patterns(self) -> list[str]: |
| 24 | - return ["artisan", "Illuminate", "app/Http/Controllers"] | |
| 24 | + return ["Illuminate"] | |
| 25 | + | |
| 26 | + @property | |
| 27 | + def detection_files(self) -> list[str]: | |
| 28 | + return ["artisan"] | |
| 25 | 29 | |
| 26 | 30 | def enrich(self) -> EnrichmentResult: |
| 27 | 31 | result = EnrichmentResult() |
| 28 | 32 | |
| 29 | 33 | # Path-based promotions: (file_path_fragment, semantic_type, pattern_key) |
| 30 | 34 |
| --- navegador/enrichment/laravel.py | |
| +++ navegador/enrichment/laravel.py | |
| @@ -19,11 +19,15 @@ | |
| 19 | def framework_name(self) -> str: |
| 20 | return "laravel" |
| 21 | |
| 22 | @property |
| 23 | def detection_patterns(self) -> list[str]: |
| 24 | return ["artisan", "Illuminate", "app/Http/Controllers"] |
| 25 | |
| 26 | def enrich(self) -> EnrichmentResult: |
| 27 | result = EnrichmentResult() |
| 28 | |
| 29 | # Path-based promotions: (file_path_fragment, semantic_type, pattern_key) |
| 30 |
| --- navegador/enrichment/laravel.py | |
| +++ navegador/enrichment/laravel.py | |
| @@ -19,11 +19,15 @@ | |
| 19 | def framework_name(self) -> str: |
| 20 | return "laravel" |
| 21 | |
| 22 | @property |
| 23 | def detection_patterns(self) -> list[str]: |
| 24 | return ["Illuminate"] |
| 25 | |
| 26 | @property |
| 27 | def detection_files(self) -> list[str]: |
| 28 | return ["artisan"] |
| 29 | |
| 30 | def enrich(self) -> EnrichmentResult: |
| 31 | result = EnrichmentResult() |
| 32 | |
| 33 | # Path-based promotions: (file_path_fragment, semantic_type, pattern_key) |
| 34 |
+5
-1
| --- navegador/enrichment/rails.py | ||
| +++ navegador/enrichment/rails.py | ||
| @@ -19,11 +19,15 @@ | ||
| 19 | 19 | def framework_name(self) -> str: |
| 20 | 20 | return "rails" |
| 21 | 21 | |
| 22 | 22 | @property |
| 23 | 23 | def detection_patterns(self) -> list[str]: |
| 24 | - return ["Gemfile", "config/routes.rb", "ApplicationController", "ActiveRecord"] | |
| 24 | + return ["rails", "active_record", "action_controller"] | |
| 25 | + | |
| 26 | + @property | |
| 27 | + def detection_files(self) -> list[str]: | |
| 28 | + return ["Gemfile"] | |
| 25 | 29 | |
| 26 | 30 | def enrich(self) -> EnrichmentResult: |
| 27 | 31 | result = EnrichmentResult() |
| 28 | 32 | |
| 29 | 33 | # Each tuple: (file_path_fragment, semantic_type, pattern_key) |
| 30 | 34 |
| --- navegador/enrichment/rails.py | |
| +++ navegador/enrichment/rails.py | |
| @@ -19,11 +19,15 @@ | |
| 19 | def framework_name(self) -> str: |
| 20 | return "rails" |
| 21 | |
| 22 | @property |
| 23 | def detection_patterns(self) -> list[str]: |
| 24 | return ["Gemfile", "config/routes.rb", "ApplicationController", "ActiveRecord"] |
| 25 | |
| 26 | def enrich(self) -> EnrichmentResult: |
| 27 | result = EnrichmentResult() |
| 28 | |
| 29 | # Each tuple: (file_path_fragment, semantic_type, pattern_key) |
| 30 |
| --- navegador/enrichment/rails.py | |
| +++ navegador/enrichment/rails.py | |
| @@ -19,11 +19,15 @@ | |
| 19 | def framework_name(self) -> str: |
| 20 | return "rails" |
| 21 | |
| 22 | @property |
| 23 | def detection_patterns(self) -> list[str]: |
| 24 | return ["rails", "active_record", "action_controller"] |
| 25 | |
| 26 | @property |
| 27 | def detection_files(self) -> list[str]: |
| 28 | return ["Gemfile"] |
| 29 | |
| 30 | def enrich(self) -> EnrichmentResult: |
| 31 | result = EnrichmentResult() |
| 32 | |
| 33 | # Each tuple: (file_path_fragment, semantic_type, pattern_key) |
| 34 |
+5
-1
| --- navegador/enrichment/react.py | ||
| +++ navegador/enrichment/react.py | ||
| @@ -19,11 +19,15 @@ | ||
| 19 | 19 | def framework_name(self) -> str: |
| 20 | 20 | return "react" |
| 21 | 21 | |
| 22 | 22 | @property |
| 23 | 23 | def detection_patterns(self) -> list[str]: |
| 24 | - return ["react", "React", "next.config", "next/router"] | |
| 24 | + return ["react", "react-dom", "next"] | |
| 25 | + | |
| 26 | + @property | |
| 27 | + def detection_files(self) -> list[str]: | |
| 28 | + return ["next.config.js", "next.config.ts", "next.config.mjs"] | |
| 25 | 29 | |
| 26 | 30 | def enrich(self) -> EnrichmentResult: |
| 27 | 31 | result = EnrichmentResult() |
| 28 | 32 | |
| 29 | 33 | # ── Components: functions/classes defined in .jsx or .tsx files ────── |
| 30 | 34 |
| --- navegador/enrichment/react.py | |
| +++ navegador/enrichment/react.py | |
| @@ -19,11 +19,15 @@ | |
| 19 | def framework_name(self) -> str: |
| 20 | return "react" |
| 21 | |
| 22 | @property |
| 23 | def detection_patterns(self) -> list[str]: |
| 24 | return ["react", "React", "next.config", "next/router"] |
| 25 | |
| 26 | def enrich(self) -> EnrichmentResult: |
| 27 | result = EnrichmentResult() |
| 28 | |
| 29 | # ── Components: functions/classes defined in .jsx or .tsx files ────── |
| 30 |
| --- navegador/enrichment/react.py | |
| +++ navegador/enrichment/react.py | |
| @@ -19,11 +19,15 @@ | |
| 19 | def framework_name(self) -> str: |
| 20 | return "react" |
| 21 | |
| 22 | @property |
| 23 | def detection_patterns(self) -> list[str]: |
| 24 | return ["react", "react-dom", "next"] |
| 25 | |
| 26 | @property |
| 27 | def detection_files(self) -> list[str]: |
| 28 | return ["next.config.js", "next.config.ts", "next.config.mjs"] |
| 29 | |
| 30 | def enrich(self) -> EnrichmentResult: |
| 31 | result = EnrichmentResult() |
| 32 | |
| 33 | # ── Components: functions/classes defined in .jsx or .tsx files ────── |
| 34 |
| --- navegador/enrichment/react_native.py | ||
| +++ navegador/enrichment/react_native.py | ||
| @@ -28,11 +28,15 @@ | ||
| 28 | 28 | def framework_name(self) -> str: |
| 29 | 29 | return "react-native" |
| 30 | 30 | |
| 31 | 31 | @property |
| 32 | 32 | def detection_patterns(self) -> list[str]: |
| 33 | - return ["react-native", "React Native", "expo"] | |
| 33 | + return ["react-native", "expo"] | |
| 34 | + | |
| 35 | + @property | |
| 36 | + def detection_files(self) -> list[str]: | |
| 37 | + return ["app.json"] | |
| 34 | 38 | |
| 35 | 39 | def enrich(self) -> EnrichmentResult: |
| 36 | 40 | result = EnrichmentResult() |
| 37 | 41 | |
| 38 | 42 | # ── Components: functions/classes in .jsx or .tsx files ────────────── |
| 39 | 43 |
| --- navegador/enrichment/react_native.py | |
| +++ navegador/enrichment/react_native.py | |
| @@ -28,11 +28,15 @@ | |
| 28 | def framework_name(self) -> str: |
| 29 | return "react-native" |
| 30 | |
| 31 | @property |
| 32 | def detection_patterns(self) -> list[str]: |
| 33 | return ["react-native", "React Native", "expo"] |
| 34 | |
| 35 | def enrich(self) -> EnrichmentResult: |
| 36 | result = EnrichmentResult() |
| 37 | |
| 38 | # ── Components: functions/classes in .jsx or .tsx files ────────────── |
| 39 |
| --- navegador/enrichment/react_native.py | |
| +++ navegador/enrichment/react_native.py | |
| @@ -28,11 +28,15 @@ | |
| 28 | def framework_name(self) -> str: |
| 29 | return "react-native" |
| 30 | |
| 31 | @property |
| 32 | def detection_patterns(self) -> list[str]: |
| 33 | return ["react-native", "expo"] |
| 34 | |
| 35 | @property |
| 36 | def detection_files(self) -> list[str]: |
| 37 | return ["app.json"] |
| 38 | |
| 39 | def enrich(self) -> EnrichmentResult: |
| 40 | result = EnrichmentResult() |
| 41 | |
| 42 | # ── Components: functions/classes in .jsx or .tsx files ────────────── |
| 43 |
+5
-1
| --- navegador/enrichment/spring.py | ||
| +++ navegador/enrichment/spring.py | ||
| @@ -18,11 +18,15 @@ | ||
| 18 | 18 | def framework_name(self) -> str: |
| 19 | 19 | return "spring" |
| 20 | 20 | |
| 21 | 21 | @property |
| 22 | 22 | def detection_patterns(self) -> list[str]: |
| 23 | - return ["@SpringBootApplication", "spring-boot", "application.properties"] | |
| 23 | + return ["org.springframework"] | |
| 24 | + | |
| 25 | + @property | |
| 26 | + def detection_files(self) -> list[str]: | |
| 27 | + return ["application.properties", "application.yml"] | |
| 24 | 28 | |
| 25 | 29 | def enrich(self) -> EnrichmentResult: |
| 26 | 30 | result = EnrichmentResult() |
| 27 | 31 | |
| 28 | 32 | # Each tuple: (annotation_fragment, semantic_type, pattern_key) |
| 29 | 33 |
| --- navegador/enrichment/spring.py | |
| +++ navegador/enrichment/spring.py | |
| @@ -18,11 +18,15 @@ | |
| 18 | def framework_name(self) -> str: |
| 19 | return "spring" |
| 20 | |
| 21 | @property |
| 22 | def detection_patterns(self) -> list[str]: |
| 23 | return ["@SpringBootApplication", "spring-boot", "application.properties"] |
| 24 | |
| 25 | def enrich(self) -> EnrichmentResult: |
| 26 | result = EnrichmentResult() |
| 27 | |
| 28 | # Each tuple: (annotation_fragment, semantic_type, pattern_key) |
| 29 |
| --- navegador/enrichment/spring.py | |
| +++ navegador/enrichment/spring.py | |
| @@ -18,11 +18,15 @@ | |
| 18 | def framework_name(self) -> str: |
| 19 | return "spring" |
| 20 | |
| 21 | @property |
| 22 | def detection_patterns(self) -> list[str]: |
| 23 | return ["org.springframework"] |
| 24 | |
| 25 | @property |
| 26 | def detection_files(self) -> list[str]: |
| 27 | return ["application.properties", "application.yml"] |
| 28 | |
| 29 | def enrich(self) -> EnrichmentResult: |
| 30 | result = EnrichmentResult() |
| 31 | |
| 32 | # Each tuple: (annotation_fragment, semantic_type, pattern_key) |
| 33 |
+1
-1
| --- pyproject.toml | ||
| +++ pyproject.toml | ||
| @@ -2,11 +2,11 @@ | ||
| 2 | 2 | requires = ["setuptools>=69.0", "wheel"] |
| 3 | 3 | build-backend = "setuptools.build_meta" |
| 4 | 4 | |
| 5 | 5 | [project] |
| 6 | 6 | name = "navegador" |
| 7 | -version = "0.7.3" | |
| 7 | +version = "0.7.4" | |
| 8 | 8 | description = "AST + knowledge graph context engine for AI coding agents" |
| 9 | 9 | readme = "README.md" |
| 10 | 10 | license = "MIT" |
| 11 | 11 | requires-python = ">=3.12" |
| 12 | 12 | authors = [ |
| 13 | 13 |
| --- pyproject.toml | |
| +++ pyproject.toml | |
| @@ -2,11 +2,11 @@ | |
| 2 | requires = ["setuptools>=69.0", "wheel"] |
| 3 | build-backend = "setuptools.build_meta" |
| 4 | |
| 5 | [project] |
| 6 | name = "navegador" |
| 7 | version = "0.7.3" |
| 8 | description = "AST + knowledge graph context engine for AI coding agents" |
| 9 | readme = "README.md" |
| 10 | license = "MIT" |
| 11 | requires-python = ">=3.12" |
| 12 | authors = [ |
| 13 |
| --- pyproject.toml | |
| +++ pyproject.toml | |
| @@ -2,11 +2,11 @@ | |
| 2 | requires = ["setuptools>=69.0", "wheel"] |
| 3 | build-backend = "setuptools.build_meta" |
| 4 | |
| 5 | [project] |
| 6 | name = "navegador" |
| 7 | version = "0.7.4" |
| 8 | description = "AST + knowledge graph context engine for AI coding agents" |
| 9 | readme = "README.md" |
| 10 | license = "MIT" |
| 11 | requires-python = ">=3.12" |
| 12 | authors = [ |
| 13 |
+2
-4
| --- tests/test_enrichment_base.py | ||
| +++ tests/test_enrichment_base.py | ||
| @@ -127,16 +127,14 @@ | ||
| 127 | 127 | enricher.detect() |
| 128 | 128 | |
| 129 | 129 | calls = store._graph.query.call_args_list |
| 130 | 130 | # Two patterns → two queries |
| 131 | 131 | assert len(calls) == 2 |
| 132 | - _, kwargs0 = calls[0] | |
| 133 | - _, kwargs1 = calls[1] | |
| 134 | 132 | params0 = calls[0][0][1] if len(calls[0][0]) > 1 else calls[0][1].get("params", {}) |
| 135 | 133 | params1 = calls[1][0][1] if len(calls[1][0]) > 1 else calls[1][1].get("params", {}) |
| 136 | - assert params0 == {"pattern": "mock_module"} | |
| 137 | - assert params1 == {"pattern": "mock_settings.py"} | |
| 134 | + assert params0 == {"name": "mock_module"} | |
| 135 | + assert params1 == {"name": "mock_settings.py"} | |
| 138 | 136 | |
| 139 | 137 | def test_stops_early_when_first_pattern_matches(self): |
| 140 | 138 | store = _mock_store(result_set=[[5]]) |
| 141 | 139 | enricher = MockEnricher(store) |
| 142 | 140 | assert enricher.detect() is True |
| 143 | 141 |
| --- tests/test_enrichment_base.py | |
| +++ tests/test_enrichment_base.py | |
| @@ -127,16 +127,14 @@ | |
| 127 | enricher.detect() |
| 128 | |
| 129 | calls = store._graph.query.call_args_list |
| 130 | # Two patterns → two queries |
| 131 | assert len(calls) == 2 |
| 132 | _, kwargs0 = calls[0] |
| 133 | _, kwargs1 = calls[1] |
| 134 | params0 = calls[0][0][1] if len(calls[0][0]) > 1 else calls[0][1].get("params", {}) |
| 135 | params1 = calls[1][0][1] if len(calls[1][0]) > 1 else calls[1][1].get("params", {}) |
| 136 | assert params0 == {"pattern": "mock_module"} |
| 137 | assert params1 == {"pattern": "mock_settings.py"} |
| 138 | |
| 139 | def test_stops_early_when_first_pattern_matches(self): |
| 140 | store = _mock_store(result_set=[[5]]) |
| 141 | enricher = MockEnricher(store) |
| 142 | assert enricher.detect() is True |
| 143 |
| --- tests/test_enrichment_base.py | |
| +++ tests/test_enrichment_base.py | |
| @@ -127,16 +127,14 @@ | |
| 127 | enricher.detect() |
| 128 | |
| 129 | calls = store._graph.query.call_args_list |
| 130 | # Two patterns → two queries |
| 131 | assert len(calls) == 2 |
| 132 | params0 = calls[0][0][1] if len(calls[0][0]) > 1 else calls[0][1].get("params", {}) |
| 133 | params1 = calls[1][0][1] if len(calls[1][0]) > 1 else calls[1][1].get("params", {}) |
| 134 | assert params0 == {"name": "mock_module"} |
| 135 | assert params1 == {"name": "mock_settings.py"} |
| 136 | |
| 137 | def test_stops_early_when_first_pattern_matches(self): |
| 138 | store = _mock_store(result_set=[[5]]) |
| 139 | enricher = MockEnricher(store) |
| 140 | assert enricher.detect() is True |
| 141 |
+9
-7
| --- tests/test_enrichment_django.py | ||
| +++ tests/test_enrichment_django.py | ||
| @@ -36,25 +36,25 @@ | ||
| 36 | 36 | class TestDjangoEnricherMetadata: |
| 37 | 37 | def test_framework_name(self): |
| 38 | 38 | enricher = DjangoEnricher(_mock_store()) |
| 39 | 39 | assert enricher.framework_name == "django" |
| 40 | 40 | |
| 41 | - def test_detection_patterns_contains_manage_py(self): | |
| 41 | + def test_detection_patterns_contains_django(self): | |
| 42 | 42 | enricher = DjangoEnricher(_mock_store()) |
| 43 | - assert "manage.py" in enricher.detection_patterns | |
| 43 | + assert "django" in enricher.detection_patterns | |
| 44 | 44 | |
| 45 | 45 | def test_detection_patterns_contains_django_conf(self): |
| 46 | 46 | enricher = DjangoEnricher(_mock_store()) |
| 47 | 47 | assert "django.conf" in enricher.detection_patterns |
| 48 | 48 | |
| 49 | - def test_detection_patterns_contains_settings_py(self): | |
| 49 | + def test_detection_patterns_contains_django_db(self): | |
| 50 | 50 | enricher = DjangoEnricher(_mock_store()) |
| 51 | - assert "settings.py" in enricher.detection_patterns | |
| 51 | + assert "django.db" in enricher.detection_patterns | |
| 52 | 52 | |
| 53 | - def test_detection_patterns_contains_urls_py(self): | |
| 53 | + def test_detection_patterns_contains_django_http(self): | |
| 54 | 54 | enricher = DjangoEnricher(_mock_store()) |
| 55 | - assert "urls.py" in enricher.detection_patterns | |
| 55 | + assert "django.http" in enricher.detection_patterns | |
| 56 | 56 | |
| 57 | 57 | def test_detection_patterns_is_list_of_strings(self): |
| 58 | 58 | enricher = DjangoEnricher(_mock_store()) |
| 59 | 59 | patterns = enricher.detection_patterns |
| 60 | 60 | assert isinstance(patterns, list) |
| @@ -103,11 +103,13 @@ | ||
| 103 | 103 | |
| 104 | 104 | def test_detect_tries_all_patterns_before_giving_up(self): |
| 105 | 105 | store = _mock_store(result_set=[[0]]) |
| 106 | 106 | enricher = DjangoEnricher(store) |
| 107 | 107 | enricher.detect() |
| 108 | - assert store._graph.query.call_count == len(enricher.detection_patterns) | |
| 108 | + # All detection_patterns are checked, then all detection_files | |
| 109 | + expected = len(enricher.detection_patterns) + len(enricher.detection_files) | |
| 110 | + assert store._graph.query.call_count == expected | |
| 109 | 111 | |
| 110 | 112 | |
| 111 | 113 | # ── enrich() — views ────────────────────────────────────────────────────────── |
| 112 | 114 | |
| 113 | 115 | |
| 114 | 116 |
| --- tests/test_enrichment_django.py | |
| +++ tests/test_enrichment_django.py | |
| @@ -36,25 +36,25 @@ | |
| 36 | class TestDjangoEnricherMetadata: |
| 37 | def test_framework_name(self): |
| 38 | enricher = DjangoEnricher(_mock_store()) |
| 39 | assert enricher.framework_name == "django" |
| 40 | |
| 41 | def test_detection_patterns_contains_manage_py(self): |
| 42 | enricher = DjangoEnricher(_mock_store()) |
| 43 | assert "manage.py" in enricher.detection_patterns |
| 44 | |
| 45 | def test_detection_patterns_contains_django_conf(self): |
| 46 | enricher = DjangoEnricher(_mock_store()) |
| 47 | assert "django.conf" in enricher.detection_patterns |
| 48 | |
| 49 | def test_detection_patterns_contains_settings_py(self): |
| 50 | enricher = DjangoEnricher(_mock_store()) |
| 51 | assert "settings.py" in enricher.detection_patterns |
| 52 | |
| 53 | def test_detection_patterns_contains_urls_py(self): |
| 54 | enricher = DjangoEnricher(_mock_store()) |
| 55 | assert "urls.py" in enricher.detection_patterns |
| 56 | |
| 57 | def test_detection_patterns_is_list_of_strings(self): |
| 58 | enricher = DjangoEnricher(_mock_store()) |
| 59 | patterns = enricher.detection_patterns |
| 60 | assert isinstance(patterns, list) |
| @@ -103,11 +103,13 @@ | |
| 103 | |
| 104 | def test_detect_tries_all_patterns_before_giving_up(self): |
| 105 | store = _mock_store(result_set=[[0]]) |
| 106 | enricher = DjangoEnricher(store) |
| 107 | enricher.detect() |
| 108 | assert store._graph.query.call_count == len(enricher.detection_patterns) |
| 109 | |
| 110 | |
| 111 | # ── enrich() — views ────────────────────────────────────────────────────────── |
| 112 | |
| 113 | |
| 114 |
| --- tests/test_enrichment_django.py | |
| +++ tests/test_enrichment_django.py | |
| @@ -36,25 +36,25 @@ | |
| 36 | class TestDjangoEnricherMetadata: |
| 37 | def test_framework_name(self): |
| 38 | enricher = DjangoEnricher(_mock_store()) |
| 39 | assert enricher.framework_name == "django" |
| 40 | |
| 41 | def test_detection_patterns_contains_django(self): |
| 42 | enricher = DjangoEnricher(_mock_store()) |
| 43 | assert "django" in enricher.detection_patterns |
| 44 | |
| 45 | def test_detection_patterns_contains_django_conf(self): |
| 46 | enricher = DjangoEnricher(_mock_store()) |
| 47 | assert "django.conf" in enricher.detection_patterns |
| 48 | |
| 49 | def test_detection_patterns_contains_django_db(self): |
| 50 | enricher = DjangoEnricher(_mock_store()) |
| 51 | assert "django.db" in enricher.detection_patterns |
| 52 | |
| 53 | def test_detection_patterns_contains_django_http(self): |
| 54 | enricher = DjangoEnricher(_mock_store()) |
| 55 | assert "django.http" in enricher.detection_patterns |
| 56 | |
| 57 | def test_detection_patterns_is_list_of_strings(self): |
| 58 | enricher = DjangoEnricher(_mock_store()) |
| 59 | patterns = enricher.detection_patterns |
| 60 | assert isinstance(patterns, list) |
| @@ -103,11 +103,13 @@ | |
| 103 | |
| 104 | def test_detect_tries_all_patterns_before_giving_up(self): |
| 105 | store = _mock_store(result_set=[[0]]) |
| 106 | enricher = DjangoEnricher(store) |
| 107 | enricher.detect() |
| 108 | # All detection_patterns are checked, then all detection_files |
| 109 | expected = len(enricher.detection_patterns) + len(enricher.detection_files) |
| 110 | assert store._graph.query.call_count == expected |
| 111 | |
| 112 | |
| 113 | # ── enrich() — views ────────────────────────────────────────────────────────── |
| 114 | |
| 115 | |
| 116 |
+4
-8
| --- tests/test_enrichment_express.py | ||
| +++ tests/test_enrichment_express.py | ||
| @@ -57,22 +57,18 @@ | ||
| 57 | 57 | class TestDetectionPatterns: |
| 58 | 58 | def test_contains_express_lowercase(self): |
| 59 | 59 | enricher = ExpressEnricher(_mock_store()) |
| 60 | 60 | assert "express" in enricher.detection_patterns |
| 61 | 61 | |
| 62 | - def test_contains_Express_titlecase(self): | |
| 63 | - enricher = ExpressEnricher(_mock_store()) | |
| 64 | - assert "Express" in enricher.detection_patterns | |
| 65 | - | |
| 66 | - def test_contains_app_listen(self): | |
| 67 | - enricher = ExpressEnricher(_mock_store()) | |
| 68 | - assert "app.listen" in enricher.detection_patterns | |
| 69 | - | |
| 70 | 62 | def test_returns_list(self): |
| 71 | 63 | enricher = ExpressEnricher(_mock_store()) |
| 72 | 64 | assert isinstance(enricher.detection_patterns, list) |
| 73 | 65 | |
| 66 | + def test_is_nonempty(self): | |
| 67 | + enricher = ExpressEnricher(_mock_store()) | |
| 68 | + assert len(enricher.detection_patterns) >= 1 | |
| 69 | + | |
| 74 | 70 | |
| 75 | 71 | # ── detect() ───────────────────────────────────────────────────────────────── |
| 76 | 72 | |
| 77 | 73 | |
| 78 | 74 | class TestDetect: |
| 79 | 75 |
| --- tests/test_enrichment_express.py | |
| +++ tests/test_enrichment_express.py | |
| @@ -57,22 +57,18 @@ | |
| 57 | class TestDetectionPatterns: |
| 58 | def test_contains_express_lowercase(self): |
| 59 | enricher = ExpressEnricher(_mock_store()) |
| 60 | assert "express" in enricher.detection_patterns |
| 61 | |
| 62 | def test_contains_Express_titlecase(self): |
| 63 | enricher = ExpressEnricher(_mock_store()) |
| 64 | assert "Express" in enricher.detection_patterns |
| 65 | |
| 66 | def test_contains_app_listen(self): |
| 67 | enricher = ExpressEnricher(_mock_store()) |
| 68 | assert "app.listen" in enricher.detection_patterns |
| 69 | |
| 70 | def test_returns_list(self): |
| 71 | enricher = ExpressEnricher(_mock_store()) |
| 72 | assert isinstance(enricher.detection_patterns, list) |
| 73 | |
| 74 | |
| 75 | # ── detect() ───────────────────────────────────────────────────────────────── |
| 76 | |
| 77 | |
| 78 | class TestDetect: |
| 79 |
| --- tests/test_enrichment_express.py | |
| +++ tests/test_enrichment_express.py | |
| @@ -57,22 +57,18 @@ | |
| 57 | class TestDetectionPatterns: |
| 58 | def test_contains_express_lowercase(self): |
| 59 | enricher = ExpressEnricher(_mock_store()) |
| 60 | assert "express" in enricher.detection_patterns |
| 61 | |
| 62 | def test_returns_list(self): |
| 63 | enricher = ExpressEnricher(_mock_store()) |
| 64 | assert isinstance(enricher.detection_patterns, list) |
| 65 | |
| 66 | def test_is_nonempty(self): |
| 67 | enricher = ExpressEnricher(_mock_store()) |
| 68 | assert len(enricher.detection_patterns) >= 1 |
| 69 | |
| 70 | |
| 71 | # ── detect() ───────────────────────────────────────────────────────────────── |
| 72 | |
| 73 | |
| 74 | class TestDetect: |
| 75 |
+12
-8
| --- tests/test_enrichment_fastapi.py | ||
| +++ tests/test_enrichment_fastapi.py | ||
| @@ -56,17 +56,19 @@ | ||
| 56 | 56 | |
| 57 | 57 | def test_detection_patterns_includes_fastapi_lowercase(self): |
| 58 | 58 | store = _mock_store() |
| 59 | 59 | assert "fastapi" in FastAPIEnricher(store).detection_patterns |
| 60 | 60 | |
| 61 | - def test_detection_patterns_includes_fastapi_class(self): | |
| 61 | + def test_detection_patterns_is_list_of_strings(self): | |
| 62 | 62 | store = _mock_store() |
| 63 | - assert "FastAPI" in FastAPIEnricher(store).detection_patterns | |
| 63 | + patterns = FastAPIEnricher(store).detection_patterns | |
| 64 | + assert isinstance(patterns, list) | |
| 65 | + assert all(isinstance(p, str) for p in patterns) | |
| 64 | 66 | |
| 65 | - def test_detection_patterns_includes_apirouter(self): | |
| 67 | + def test_detection_patterns_is_nonempty(self): | |
| 66 | 68 | store = _mock_store() |
| 67 | - assert "APIRouter" in FastAPIEnricher(store).detection_patterns | |
| 69 | + assert len(FastAPIEnricher(store).detection_patterns) >= 1 | |
| 68 | 70 | |
| 69 | 71 | def test_is_subclass_of_framework_enricher(self): |
| 70 | 72 | store = _mock_store() |
| 71 | 73 | assert isinstance(FastAPIEnricher(store), FrameworkEnricher) |
| 72 | 74 | |
| @@ -85,15 +87,17 @@ | ||
| 85 | 87 | |
| 86 | 88 | def test_detect_returns_false_on_empty_result_set(self): |
| 87 | 89 | store = _mock_store(result_set=[]) |
| 88 | 90 | assert FastAPIEnricher(store).detect() is False |
| 89 | 91 | |
| 90 | - def test_detect_queries_all_three_patterns_when_no_match(self): | |
| 92 | + def test_detect_queries_all_patterns_when_no_match(self): | |
| 91 | 93 | store = _mock_store(result_set=[[0]]) |
| 92 | - FastAPIEnricher(store).detect() | |
| 93 | - # Three detection patterns → three queries | |
| 94 | - assert store._graph.query.call_count == 3 | |
| 94 | + enricher = FastAPIEnricher(store) | |
| 95 | + enricher.detect() | |
| 96 | + # All detection_patterns queried when no match is found | |
| 97 | + expected = len(enricher.detection_patterns) + len(enricher.detection_files) | |
| 98 | + assert store._graph.query.call_count == expected | |
| 95 | 99 | |
| 96 | 100 | def test_detect_short_circuits_on_first_match(self): |
| 97 | 101 | store = _mock_store(result_set=[[7]]) |
| 98 | 102 | FastAPIEnricher(store).detect() |
| 99 | 103 | assert store._graph.query.call_count == 1 |
| 100 | 104 |
| --- tests/test_enrichment_fastapi.py | |
| +++ tests/test_enrichment_fastapi.py | |
| @@ -56,17 +56,19 @@ | |
| 56 | |
| 57 | def test_detection_patterns_includes_fastapi_lowercase(self): |
| 58 | store = _mock_store() |
| 59 | assert "fastapi" in FastAPIEnricher(store).detection_patterns |
| 60 | |
| 61 | def test_detection_patterns_includes_fastapi_class(self): |
| 62 | store = _mock_store() |
| 63 | assert "FastAPI" in FastAPIEnricher(store).detection_patterns |
| 64 | |
| 65 | def test_detection_patterns_includes_apirouter(self): |
| 66 | store = _mock_store() |
| 67 | assert "APIRouter" in FastAPIEnricher(store).detection_patterns |
| 68 | |
| 69 | def test_is_subclass_of_framework_enricher(self): |
| 70 | store = _mock_store() |
| 71 | assert isinstance(FastAPIEnricher(store), FrameworkEnricher) |
| 72 | |
| @@ -85,15 +87,17 @@ | |
| 85 | |
| 86 | def test_detect_returns_false_on_empty_result_set(self): |
| 87 | store = _mock_store(result_set=[]) |
| 88 | assert FastAPIEnricher(store).detect() is False |
| 89 | |
| 90 | def test_detect_queries_all_three_patterns_when_no_match(self): |
| 91 | store = _mock_store(result_set=[[0]]) |
| 92 | FastAPIEnricher(store).detect() |
| 93 | # Three detection patterns → three queries |
| 94 | assert store._graph.query.call_count == 3 |
| 95 | |
| 96 | def test_detect_short_circuits_on_first_match(self): |
| 97 | store = _mock_store(result_set=[[7]]) |
| 98 | FastAPIEnricher(store).detect() |
| 99 | assert store._graph.query.call_count == 1 |
| 100 |
| --- tests/test_enrichment_fastapi.py | |
| +++ tests/test_enrichment_fastapi.py | |
| @@ -56,17 +56,19 @@ | |
| 56 | |
| 57 | def test_detection_patterns_includes_fastapi_lowercase(self): |
| 58 | store = _mock_store() |
| 59 | assert "fastapi" in FastAPIEnricher(store).detection_patterns |
| 60 | |
| 61 | def test_detection_patterns_is_list_of_strings(self): |
| 62 | store = _mock_store() |
| 63 | patterns = FastAPIEnricher(store).detection_patterns |
| 64 | assert isinstance(patterns, list) |
| 65 | assert all(isinstance(p, str) for p in patterns) |
| 66 | |
| 67 | def test_detection_patterns_is_nonempty(self): |
| 68 | store = _mock_store() |
| 69 | assert len(FastAPIEnricher(store).detection_patterns) >= 1 |
| 70 | |
| 71 | def test_is_subclass_of_framework_enricher(self): |
| 72 | store = _mock_store() |
| 73 | assert isinstance(FastAPIEnricher(store), FrameworkEnricher) |
| 74 | |
| @@ -85,15 +87,17 @@ | |
| 87 | |
| 88 | def test_detect_returns_false_on_empty_result_set(self): |
| 89 | store = _mock_store(result_set=[]) |
| 90 | assert FastAPIEnricher(store).detect() is False |
| 91 | |
| 92 | def test_detect_queries_all_patterns_when_no_match(self): |
| 93 | store = _mock_store(result_set=[[0]]) |
| 94 | enricher = FastAPIEnricher(store) |
| 95 | enricher.detect() |
| 96 | # All detection_patterns queried when no match is found |
| 97 | expected = len(enricher.detection_patterns) + len(enricher.detection_files) |
| 98 | assert store._graph.query.call_count == expected |
| 99 | |
| 100 | def test_detect_short_circuits_on_first_match(self): |
| 101 | store = _mock_store(result_set=[[7]]) |
| 102 | FastAPIEnricher(store).detect() |
| 103 | assert store._graph.query.call_count == 1 |
| 104 |
+8
-8
| --- tests/test_enrichment_laravel.py | ||
| +++ tests/test_enrichment_laravel.py | ||
| @@ -39,25 +39,25 @@ | ||
| 39 | 39 | assert LaravelEnricher(store).framework_name == "laravel" |
| 40 | 40 | |
| 41 | 41 | def test_is_framework_enricher_subclass(self): |
| 42 | 42 | assert issubclass(LaravelEnricher, FrameworkEnricher) |
| 43 | 43 | |
| 44 | - def test_detection_patterns_contains_artisan(self): | |
| 45 | - store = _mock_store() | |
| 46 | - assert "artisan" in LaravelEnricher(store).detection_patterns | |
| 47 | - | |
| 48 | 44 | def test_detection_patterns_contains_illuminate(self): |
| 49 | 45 | store = _mock_store() |
| 50 | 46 | assert "Illuminate" in LaravelEnricher(store).detection_patterns |
| 51 | 47 | |
| 52 | - def test_detection_patterns_contains_http_controllers(self): | |
| 48 | + def test_detection_files_contains_artisan(self): | |
| 49 | + store = _mock_store() | |
| 50 | + assert "artisan" in LaravelEnricher(store).detection_files | |
| 51 | + | |
| 52 | + def test_detection_patterns_has_one_entry(self): | |
| 53 | 53 | store = _mock_store() |
| 54 | - assert "app/Http/Controllers" in LaravelEnricher(store).detection_patterns | |
| 54 | + assert len(LaravelEnricher(store).detection_patterns) == 1 | |
| 55 | 55 | |
| 56 | - def test_detection_patterns_has_three_entries(self): | |
| 56 | + def test_detection_files_is_nonempty(self): | |
| 57 | 57 | store = _mock_store() |
| 58 | - assert len(LaravelEnricher(store).detection_patterns) == 3 | |
| 58 | + assert len(LaravelEnricher(store).detection_files) >= 1 | |
| 59 | 59 | |
| 60 | 60 | |
| 61 | 61 | # ── enrich() return type ────────────────────────────────────────────────────── |
| 62 | 62 | |
| 63 | 63 | |
| 64 | 64 |
| --- tests/test_enrichment_laravel.py | |
| +++ tests/test_enrichment_laravel.py | |
| @@ -39,25 +39,25 @@ | |
| 39 | assert LaravelEnricher(store).framework_name == "laravel" |
| 40 | |
| 41 | def test_is_framework_enricher_subclass(self): |
| 42 | assert issubclass(LaravelEnricher, FrameworkEnricher) |
| 43 | |
| 44 | def test_detection_patterns_contains_artisan(self): |
| 45 | store = _mock_store() |
| 46 | assert "artisan" in LaravelEnricher(store).detection_patterns |
| 47 | |
| 48 | def test_detection_patterns_contains_illuminate(self): |
| 49 | store = _mock_store() |
| 50 | assert "Illuminate" in LaravelEnricher(store).detection_patterns |
| 51 | |
| 52 | def test_detection_patterns_contains_http_controllers(self): |
| 53 | store = _mock_store() |
| 54 | assert "app/Http/Controllers" in LaravelEnricher(store).detection_patterns |
| 55 | |
| 56 | def test_detection_patterns_has_three_entries(self): |
| 57 | store = _mock_store() |
| 58 | assert len(LaravelEnricher(store).detection_patterns) == 3 |
| 59 | |
| 60 | |
| 61 | # ── enrich() return type ────────────────────────────────────────────────────── |
| 62 | |
| 63 | |
| 64 |
| --- tests/test_enrichment_laravel.py | |
| +++ tests/test_enrichment_laravel.py | |
| @@ -39,25 +39,25 @@ | |
| 39 | assert LaravelEnricher(store).framework_name == "laravel" |
| 40 | |
| 41 | def test_is_framework_enricher_subclass(self): |
| 42 | assert issubclass(LaravelEnricher, FrameworkEnricher) |
| 43 | |
| 44 | def test_detection_patterns_contains_illuminate(self): |
| 45 | store = _mock_store() |
| 46 | assert "Illuminate" in LaravelEnricher(store).detection_patterns |
| 47 | |
| 48 | def test_detection_files_contains_artisan(self): |
| 49 | store = _mock_store() |
| 50 | assert "artisan" in LaravelEnricher(store).detection_files |
| 51 | |
| 52 | def test_detection_patterns_has_one_entry(self): |
| 53 | store = _mock_store() |
| 54 | assert len(LaravelEnricher(store).detection_patterns) == 1 |
| 55 | |
| 56 | def test_detection_files_is_nonempty(self): |
| 57 | store = _mock_store() |
| 58 | assert len(LaravelEnricher(store).detection_files) >= 1 |
| 59 | |
| 60 | |
| 61 | # ── enrich() return type ────────────────────────────────────────────────────── |
| 62 | |
| 63 | |
| 64 |
+14
-14
| --- tests/test_enrichment_rails.py | ||
| +++ tests/test_enrichment_rails.py | ||
| @@ -39,29 +39,29 @@ | ||
| 39 | 39 | assert RailsEnricher(store).framework_name == "rails" |
| 40 | 40 | |
| 41 | 41 | def test_is_framework_enricher_subclass(self): |
| 42 | 42 | assert issubclass(RailsEnricher, FrameworkEnricher) |
| 43 | 43 | |
| 44 | - def test_detection_patterns_contains_gemfile(self): | |
| 45 | - store = _mock_store() | |
| 46 | - assert "Gemfile" in RailsEnricher(store).detection_patterns | |
| 47 | - | |
| 48 | - def test_detection_patterns_contains_routes(self): | |
| 49 | - store = _mock_store() | |
| 50 | - assert "config/routes.rb" in RailsEnricher(store).detection_patterns | |
| 51 | - | |
| 52 | - def test_detection_patterns_contains_application_controller(self): | |
| 53 | - store = _mock_store() | |
| 54 | - assert "ApplicationController" in RailsEnricher(store).detection_patterns | |
| 44 | + def test_detection_patterns_contains_rails(self): | |
| 45 | + store = _mock_store() | |
| 46 | + assert "rails" in RailsEnricher(store).detection_patterns | |
| 55 | 47 | |
| 56 | 48 | def test_detection_patterns_contains_active_record(self): |
| 57 | 49 | store = _mock_store() |
| 58 | - assert "ActiveRecord" in RailsEnricher(store).detection_patterns | |
| 50 | + assert "active_record" in RailsEnricher(store).detection_patterns | |
| 51 | + | |
| 52 | + def test_detection_patterns_contains_action_controller(self): | |
| 53 | + store = _mock_store() | |
| 54 | + assert "action_controller" in RailsEnricher(store).detection_patterns | |
| 55 | + | |
| 56 | + def test_detection_files_contains_gemfile(self): | |
| 57 | + store = _mock_store() | |
| 58 | + assert "Gemfile" in RailsEnricher(store).detection_files | |
| 59 | 59 | |
| 60 | - def test_detection_patterns_has_four_entries(self): | |
| 60 | + def test_detection_patterns_has_three_entries(self): | |
| 61 | 61 | store = _mock_store() |
| 62 | - assert len(RailsEnricher(store).detection_patterns) == 4 | |
| 62 | + assert len(RailsEnricher(store).detection_patterns) == 3 | |
| 63 | 63 | |
| 64 | 64 | |
| 65 | 65 | # ── enrich() return type ────────────────────────────────────────────────────── |
| 66 | 66 | |
| 67 | 67 | |
| 68 | 68 |
| --- tests/test_enrichment_rails.py | |
| +++ tests/test_enrichment_rails.py | |
| @@ -39,29 +39,29 @@ | |
| 39 | assert RailsEnricher(store).framework_name == "rails" |
| 40 | |
| 41 | def test_is_framework_enricher_subclass(self): |
| 42 | assert issubclass(RailsEnricher, FrameworkEnricher) |
| 43 | |
| 44 | def test_detection_patterns_contains_gemfile(self): |
| 45 | store = _mock_store() |
| 46 | assert "Gemfile" in RailsEnricher(store).detection_patterns |
| 47 | |
| 48 | def test_detection_patterns_contains_routes(self): |
| 49 | store = _mock_store() |
| 50 | assert "config/routes.rb" in RailsEnricher(store).detection_patterns |
| 51 | |
| 52 | def test_detection_patterns_contains_application_controller(self): |
| 53 | store = _mock_store() |
| 54 | assert "ApplicationController" in RailsEnricher(store).detection_patterns |
| 55 | |
| 56 | def test_detection_patterns_contains_active_record(self): |
| 57 | store = _mock_store() |
| 58 | assert "ActiveRecord" in RailsEnricher(store).detection_patterns |
| 59 | |
| 60 | def test_detection_patterns_has_four_entries(self): |
| 61 | store = _mock_store() |
| 62 | assert len(RailsEnricher(store).detection_patterns) == 4 |
| 63 | |
| 64 | |
| 65 | # ── enrich() return type ────────────────────────────────────────────────────── |
| 66 | |
| 67 | |
| 68 |
| --- tests/test_enrichment_rails.py | |
| +++ tests/test_enrichment_rails.py | |
| @@ -39,29 +39,29 @@ | |
| 39 | assert RailsEnricher(store).framework_name == "rails" |
| 40 | |
| 41 | def test_is_framework_enricher_subclass(self): |
| 42 | assert issubclass(RailsEnricher, FrameworkEnricher) |
| 43 | |
| 44 | def test_detection_patterns_contains_rails(self): |
| 45 | store = _mock_store() |
| 46 | assert "rails" in RailsEnricher(store).detection_patterns |
| 47 | |
| 48 | def test_detection_patterns_contains_active_record(self): |
| 49 | store = _mock_store() |
| 50 | assert "active_record" in RailsEnricher(store).detection_patterns |
| 51 | |
| 52 | def test_detection_patterns_contains_action_controller(self): |
| 53 | store = _mock_store() |
| 54 | assert "action_controller" in RailsEnricher(store).detection_patterns |
| 55 | |
| 56 | def test_detection_files_contains_gemfile(self): |
| 57 | store = _mock_store() |
| 58 | assert "Gemfile" in RailsEnricher(store).detection_files |
| 59 | |
| 60 | def test_detection_patterns_has_three_entries(self): |
| 61 | store = _mock_store() |
| 62 | assert len(RailsEnricher(store).detection_patterns) == 3 |
| 63 | |
| 64 | |
| 65 | # ── enrich() return type ────────────────────────────────────────────────────── |
| 66 | |
| 67 | |
| 68 |
+12
-11
| --- tests/test_enrichment_react.py | ||
| +++ tests/test_enrichment_react.py | ||
| @@ -59,25 +59,26 @@ | ||
| 59 | 59 | class TestDetectionPatterns: |
| 60 | 60 | def test_contains_react(self): |
| 61 | 61 | enricher = ReactEnricher(_mock_store()) |
| 62 | 62 | assert "react" in enricher.detection_patterns |
| 63 | 63 | |
| 64 | - def test_contains_React(self): | |
| 65 | - enricher = ReactEnricher(_mock_store()) | |
| 66 | - assert "React" in enricher.detection_patterns | |
| 67 | - | |
| 68 | - def test_contains_next_config(self): | |
| 69 | - enricher = ReactEnricher(_mock_store()) | |
| 70 | - assert "next.config" in enricher.detection_patterns | |
| 71 | - | |
| 72 | - def test_contains_next_router(self): | |
| 73 | - enricher = ReactEnricher(_mock_store()) | |
| 74 | - assert "next/router" in enricher.detection_patterns | |
| 64 | + def test_contains_react_dom(self): | |
| 65 | + enricher = ReactEnricher(_mock_store()) | |
| 66 | + assert "react-dom" in enricher.detection_patterns | |
| 67 | + | |
| 68 | + def test_contains_next(self): | |
| 69 | + enricher = ReactEnricher(_mock_store()) | |
| 70 | + assert "next" in enricher.detection_patterns | |
| 75 | 71 | |
| 76 | 72 | def test_returns_list(self): |
| 77 | 73 | enricher = ReactEnricher(_mock_store()) |
| 78 | 74 | assert isinstance(enricher.detection_patterns, list) |
| 75 | + | |
| 76 | + def test_detection_files_contains_next_config_variants(self): | |
| 77 | + enricher = ReactEnricher(_mock_store()) | |
| 78 | + files = enricher.detection_files | |
| 79 | + assert any("next.config" in f for f in files) | |
| 79 | 80 | |
| 80 | 81 | |
| 81 | 82 | # ── detect() ───────────────────────────────────────────────────────────────── |
| 82 | 83 | |
| 83 | 84 | |
| 84 | 85 |
| --- tests/test_enrichment_react.py | |
| +++ tests/test_enrichment_react.py | |
| @@ -59,25 +59,26 @@ | |
| 59 | class TestDetectionPatterns: |
| 60 | def test_contains_react(self): |
| 61 | enricher = ReactEnricher(_mock_store()) |
| 62 | assert "react" in enricher.detection_patterns |
| 63 | |
| 64 | def test_contains_React(self): |
| 65 | enricher = ReactEnricher(_mock_store()) |
| 66 | assert "React" in enricher.detection_patterns |
| 67 | |
| 68 | def test_contains_next_config(self): |
| 69 | enricher = ReactEnricher(_mock_store()) |
| 70 | assert "next.config" in enricher.detection_patterns |
| 71 | |
| 72 | def test_contains_next_router(self): |
| 73 | enricher = ReactEnricher(_mock_store()) |
| 74 | assert "next/router" in enricher.detection_patterns |
| 75 | |
| 76 | def test_returns_list(self): |
| 77 | enricher = ReactEnricher(_mock_store()) |
| 78 | assert isinstance(enricher.detection_patterns, list) |
| 79 | |
| 80 | |
| 81 | # ── detect() ───────────────────────────────────────────────────────────────── |
| 82 | |
| 83 | |
| 84 |
| --- tests/test_enrichment_react.py | |
| +++ tests/test_enrichment_react.py | |
| @@ -59,25 +59,26 @@ | |
| 59 | class TestDetectionPatterns: |
| 60 | def test_contains_react(self): |
| 61 | enricher = ReactEnricher(_mock_store()) |
| 62 | assert "react" in enricher.detection_patterns |
| 63 | |
| 64 | def test_contains_react_dom(self): |
| 65 | enricher = ReactEnricher(_mock_store()) |
| 66 | assert "react-dom" in enricher.detection_patterns |
| 67 | |
| 68 | def test_contains_next(self): |
| 69 | enricher = ReactEnricher(_mock_store()) |
| 70 | assert "next" in enricher.detection_patterns |
| 71 | |
| 72 | def test_returns_list(self): |
| 73 | enricher = ReactEnricher(_mock_store()) |
| 74 | assert isinstance(enricher.detection_patterns, list) |
| 75 | |
| 76 | def test_detection_files_contains_next_config_variants(self): |
| 77 | enricher = ReactEnricher(_mock_store()) |
| 78 | files = enricher.detection_files |
| 79 | assert any("next.config" in f for f in files) |
| 80 | |
| 81 | |
| 82 | # ── detect() ───────────────────────────────────────────────────────────────── |
| 83 | |
| 84 | |
| 85 |
| --- tests/test_enrichment_react_native.py | ||
| +++ tests/test_enrichment_react_native.py | ||
| @@ -57,22 +57,22 @@ | ||
| 57 | 57 | class TestDetectionPatterns: |
| 58 | 58 | def test_contains_react_native_hyphenated(self): |
| 59 | 59 | enricher = ReactNativeEnricher(_mock_store()) |
| 60 | 60 | assert "react-native" in enricher.detection_patterns |
| 61 | 61 | |
| 62 | - def test_contains_React_Native_titlecase(self): | |
| 63 | - enricher = ReactNativeEnricher(_mock_store()) | |
| 64 | - assert "React Native" in enricher.detection_patterns | |
| 65 | - | |
| 66 | 62 | def test_contains_expo(self): |
| 67 | 63 | enricher = ReactNativeEnricher(_mock_store()) |
| 68 | 64 | assert "expo" in enricher.detection_patterns |
| 69 | 65 | |
| 70 | 66 | def test_returns_list(self): |
| 71 | 67 | enricher = ReactNativeEnricher(_mock_store()) |
| 72 | 68 | assert isinstance(enricher.detection_patterns, list) |
| 73 | 69 | |
| 70 | + def test_has_at_least_two_patterns(self): | |
| 71 | + enricher = ReactNativeEnricher(_mock_store()) | |
| 72 | + assert len(enricher.detection_patterns) >= 2 | |
| 73 | + | |
| 74 | 74 | |
| 75 | 75 | # ── detect() ───────────────────────────────────────────────────────────────── |
| 76 | 76 | |
| 77 | 77 | |
| 78 | 78 | class TestDetect: |
| 79 | 79 |
| --- tests/test_enrichment_react_native.py | |
| +++ tests/test_enrichment_react_native.py | |
| @@ -57,22 +57,22 @@ | |
| 57 | class TestDetectionPatterns: |
| 58 | def test_contains_react_native_hyphenated(self): |
| 59 | enricher = ReactNativeEnricher(_mock_store()) |
| 60 | assert "react-native" in enricher.detection_patterns |
| 61 | |
| 62 | def test_contains_React_Native_titlecase(self): |
| 63 | enricher = ReactNativeEnricher(_mock_store()) |
| 64 | assert "React Native" in enricher.detection_patterns |
| 65 | |
| 66 | def test_contains_expo(self): |
| 67 | enricher = ReactNativeEnricher(_mock_store()) |
| 68 | assert "expo" in enricher.detection_patterns |
| 69 | |
| 70 | def test_returns_list(self): |
| 71 | enricher = ReactNativeEnricher(_mock_store()) |
| 72 | assert isinstance(enricher.detection_patterns, list) |
| 73 | |
| 74 | |
| 75 | # ── detect() ───────────────────────────────────────────────────────────────── |
| 76 | |
| 77 | |
| 78 | class TestDetect: |
| 79 |
| --- tests/test_enrichment_react_native.py | |
| +++ tests/test_enrichment_react_native.py | |
| @@ -57,22 +57,22 @@ | |
| 57 | class TestDetectionPatterns: |
| 58 | def test_contains_react_native_hyphenated(self): |
| 59 | enricher = ReactNativeEnricher(_mock_store()) |
| 60 | assert "react-native" in enricher.detection_patterns |
| 61 | |
| 62 | def test_contains_expo(self): |
| 63 | enricher = ReactNativeEnricher(_mock_store()) |
| 64 | assert "expo" in enricher.detection_patterns |
| 65 | |
| 66 | def test_returns_list(self): |
| 67 | enricher = ReactNativeEnricher(_mock_store()) |
| 68 | assert isinstance(enricher.detection_patterns, list) |
| 69 | |
| 70 | def test_has_at_least_two_patterns(self): |
| 71 | enricher = ReactNativeEnricher(_mock_store()) |
| 72 | assert len(enricher.detection_patterns) >= 2 |
| 73 | |
| 74 | |
| 75 | # ── detect() ───────────────────────────────────────────────────────────────── |
| 76 | |
| 77 | |
| 78 | class TestDetect: |
| 79 |
+15
-15
| --- tests/test_enrichment_spring.py | ||
| +++ tests/test_enrichment_spring.py | ||
| @@ -39,25 +39,25 @@ | ||
| 39 | 39 | assert SpringEnricher(store).framework_name == "spring" |
| 40 | 40 | |
| 41 | 41 | def test_is_framework_enricher_subclass(self): |
| 42 | 42 | assert issubclass(SpringEnricher, FrameworkEnricher) |
| 43 | 43 | |
| 44 | - def test_detection_patterns_contains_spring_boot_application(self): | |
| 45 | - store = _mock_store() | |
| 46 | - assert "@SpringBootApplication" in SpringEnricher(store).detection_patterns | |
| 47 | - | |
| 48 | - def test_detection_patterns_contains_spring_boot(self): | |
| 49 | - store = _mock_store() | |
| 50 | - assert "spring-boot" in SpringEnricher(store).detection_patterns | |
| 51 | - | |
| 52 | - def test_detection_patterns_contains_application_properties(self): | |
| 53 | - store = _mock_store() | |
| 54 | - assert "application.properties" in SpringEnricher(store).detection_patterns | |
| 55 | - | |
| 56 | - def test_detection_patterns_has_three_entries(self): | |
| 57 | - store = _mock_store() | |
| 58 | - assert len(SpringEnricher(store).detection_patterns) == 3 | |
| 44 | + def test_detection_patterns_contains_org_springframework(self): | |
| 45 | + store = _mock_store() | |
| 46 | + assert "org.springframework" in SpringEnricher(store).detection_patterns | |
| 47 | + | |
| 48 | + def test_detection_files_contains_application_properties(self): | |
| 49 | + store = _mock_store() | |
| 50 | + assert "application.properties" in SpringEnricher(store).detection_files | |
| 51 | + | |
| 52 | + def test_detection_files_contains_application_yml(self): | |
| 53 | + store = _mock_store() | |
| 54 | + assert "application.yml" in SpringEnricher(store).detection_files | |
| 55 | + | |
| 56 | + def test_detection_patterns_has_one_entry(self): | |
| 57 | + store = _mock_store() | |
| 58 | + assert len(SpringEnricher(store).detection_patterns) == 1 | |
| 59 | 59 | |
| 60 | 60 | |
| 61 | 61 | # ── enrich() return type ────────────────────────────────────────────────────── |
| 62 | 62 | |
| 63 | 63 | |
| 64 | 64 |
| --- tests/test_enrichment_spring.py | |
| +++ tests/test_enrichment_spring.py | |
| @@ -39,25 +39,25 @@ | |
| 39 | assert SpringEnricher(store).framework_name == "spring" |
| 40 | |
| 41 | def test_is_framework_enricher_subclass(self): |
| 42 | assert issubclass(SpringEnricher, FrameworkEnricher) |
| 43 | |
| 44 | def test_detection_patterns_contains_spring_boot_application(self): |
| 45 | store = _mock_store() |
| 46 | assert "@SpringBootApplication" in SpringEnricher(store).detection_patterns |
| 47 | |
| 48 | def test_detection_patterns_contains_spring_boot(self): |
| 49 | store = _mock_store() |
| 50 | assert "spring-boot" in SpringEnricher(store).detection_patterns |
| 51 | |
| 52 | def test_detection_patterns_contains_application_properties(self): |
| 53 | store = _mock_store() |
| 54 | assert "application.properties" in SpringEnricher(store).detection_patterns |
| 55 | |
| 56 | def test_detection_patterns_has_three_entries(self): |
| 57 | store = _mock_store() |
| 58 | assert len(SpringEnricher(store).detection_patterns) == 3 |
| 59 | |
| 60 | |
| 61 | # ── enrich() return type ────────────────────────────────────────────────────── |
| 62 | |
| 63 | |
| 64 |
| --- tests/test_enrichment_spring.py | |
| +++ tests/test_enrichment_spring.py | |
| @@ -39,25 +39,25 @@ | |
| 39 | assert SpringEnricher(store).framework_name == "spring" |
| 40 | |
| 41 | def test_is_framework_enricher_subclass(self): |
| 42 | assert issubclass(SpringEnricher, FrameworkEnricher) |
| 43 | |
| 44 | def test_detection_patterns_contains_org_springframework(self): |
| 45 | store = _mock_store() |
| 46 | assert "org.springframework" in SpringEnricher(store).detection_patterns |
| 47 | |
| 48 | def test_detection_files_contains_application_properties(self): |
| 49 | store = _mock_store() |
| 50 | assert "application.properties" in SpringEnricher(store).detection_files |
| 51 | |
| 52 | def test_detection_files_contains_application_yml(self): |
| 53 | store = _mock_store() |
| 54 | assert "application.yml" in SpringEnricher(store).detection_files |
| 55 | |
| 56 | def test_detection_patterns_has_one_entry(self): |
| 57 | store = _mock_store() |
| 58 | assert len(SpringEnricher(store).detection_patterns) == 1 |
| 59 | |
| 60 | |
| 61 | # ── enrich() return type ────────────────────────────────────────────────────── |
| 62 | |
| 63 | |
| 64 |