|
| 1 | +################################################################################ |
| 2 | +# Copyright IBM Corporation 2024, 2025 |
| 3 | +# |
| 4 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | +# you may not use this file except in compliance with the License. |
| 6 | +# You may obtain a copy of the License at |
| 7 | +# |
| 8 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | +# |
| 10 | +# Unless required by applicable law or agreed to in writing, software |
| 11 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | +# See the License for the specific language governing permissions and |
| 14 | +# limitations under the License. |
| 15 | +################################################################################ |
| 16 | + |
| 17 | +"""Regression tests for #231: reconstructing External (phantom) nodes from Neo4j |
| 18 | +properties must conform to the slim TSExternalSymbol model (name + module only) — |
| 19 | +the graph node legitimately carries signature/kind properties, and the |
| 20 | +reconstructor must not forward them into an extra='forbid' model.""" |
| 21 | + |
| 22 | +from unittest.mock import patch |
| 23 | + |
| 24 | +from cldk.analysis.typescript.neo4j import reconstruct as R |
| 25 | +from cldk.analysis.typescript.neo4j.neo4j_backend import TSNeo4jBackend |
| 26 | +from cldk.models.typescript import TSExternalSymbol |
| 27 | + |
| 28 | + |
| 29 | +def test_external_reconstructs_from_full_graph_props(): |
| 30 | + """A real External node's property bag (signature, kind, _module included) reconstructs.""" |
| 31 | + sym = R.external( |
| 32 | + { |
| 33 | + "signature": "commander.parse", |
| 34 | + "name": "parse", |
| 35 | + "module": "commander", |
| 36 | + "kind": "external", |
| 37 | + "_module": "app", |
| 38 | + } |
| 39 | + ) |
| 40 | + assert isinstance(sym, TSExternalSymbol) |
| 41 | + assert sym.name == "parse" |
| 42 | + assert sym.module == "commander" |
| 43 | + |
| 44 | + |
| 45 | +def test_external_reconstructs_with_empty_signature(): |
| 46 | + """The reported repro: an empty-signature External node must not raise.""" |
| 47 | + sym = R.external({"signature": "", "name": "", "module": "", "kind": "unknown"}) |
| 48 | + assert isinstance(sym, TSExternalSymbol) |
| 49 | + |
| 50 | + |
| 51 | +def test_get_external_symbols_end_to_end_via_stubbed_run(): |
| 52 | + """Backend-level: get_external_symbols reconstructs every returned row, keyed by signature.""" |
| 53 | + rows = [ |
| 54 | + {"p": {"signature": "commander.parse", "name": "parse", "module": "commander", "kind": "external"}}, |
| 55 | + {"p": {"signature": "fs.readFileSync", "name": "readFileSync", "module": "fs", "kind": "external"}}, |
| 56 | + ] |
| 57 | + backend = TSNeo4jBackend.__new__(TSNeo4jBackend) |
| 58 | + backend._modules = ["app"] |
| 59 | + with patch.object(TSNeo4jBackend, "_run", return_value=rows): |
| 60 | + out = backend.get_external_symbols() |
| 61 | + assert set(out) == {"commander.parse", "fs.readFileSync"} |
| 62 | + assert out["commander.parse"].module == "commander" |
0 commit comments