Parse and validate AT Protocol Lexicons with DTO generation for Laravel
1<?php
2
3namespace SocialDept\AtpSchema\Tests\Unit\Generator;
4
5use Orchestra\Testbench\TestCase;
6use SocialDept\AtpSchema\Generator\NamingConverter;
7
8class NamingConverterTest extends TestCase
9{
10 protected NamingConverter $converter;
11
12 protected function setUp(): void
13 {
14 parent::setUp();
15
16 $this->converter = new NamingConverter('App\\Lexicon');
17 }
18
19 public function test_it_converts_nsid_to_class_name(): void
20 {
21 $className = $this->converter->nsidToClassName('app.bsky.feed.post');
22
23 $this->assertSame('App\\Lexicon\\App\\Bsky\\Feed\\Post', $className);
24 }
25
26 public function test_it_converts_nsid_to_namespace(): void
27 {
28 $namespace = $this->converter->nsidToNamespace('app.bsky.feed.post');
29
30 $this->assertSame('App\\Lexicon\\App\\Bsky\\Feed', $namespace);
31 }
32
33 public function test_it_handles_multi_part_names(): void
34 {
35 $className = $this->converter->toClassName('feed.post');
36
37 $this->assertSame('FeedPost', $className);
38 }
39
40 public function test_it_handles_single_part_names(): void
41 {
42 $className = $this->converter->toClassName('post');
43
44 $this->assertSame('Post', $className);
45 }
46
47 public function test_it_converts_to_pascal_case(): void
48 {
49 $this->assertSame('HelloWorld', $this->converter->toPascalCase('hello_world'));
50 $this->assertSame('HelloWorld', $this->converter->toPascalCase('hello-world'));
51 $this->assertSame('HelloWorld', $this->converter->toPascalCase('hello world'));
52 $this->assertSame('HelloWorld', $this->converter->toPascalCase('helloWorld'));
53 $this->assertSame('Foo', $this->converter->toPascalCase('foo'));
54 }
55
56 public function test_it_converts_to_camel_case(): void
57 {
58 $this->assertSame('helloWorld', $this->converter->toCamelCase('hello_world'));
59 $this->assertSame('helloWorld', $this->converter->toCamelCase('hello-world'));
60 $this->assertSame('helloWorld', $this->converter->toCamelCase('hello world'));
61 $this->assertSame('helloWorld', $this->converter->toCamelCase('HelloWorld'));
62 $this->assertSame('foo', $this->converter->toCamelCase('foo'));
63 }
64
65 public function test_it_converts_to_snake_case(): void
66 {
67 $this->assertSame('hello_world', $this->converter->toSnakeCase('HelloWorld'));
68 $this->assertSame('hello_world', $this->converter->toSnakeCase('helloWorld'));
69 $this->assertSame('foo', $this->converter->toSnakeCase('foo'));
70 $this->assertSame('foo_bar_baz', $this->converter->toSnakeCase('FooBarBaz'));
71 }
72
73 public function test_it_converts_to_kebab_case(): void
74 {
75 $this->assertSame('hello-world', $this->converter->toKebabCase('HelloWorld'));
76 $this->assertSame('hello-world', $this->converter->toKebabCase('helloWorld'));
77 $this->assertSame('foo', $this->converter->toKebabCase('foo'));
78 $this->assertSame('foo-bar-baz', $this->converter->toKebabCase('FooBarBaz'));
79 }
80
81 public function test_it_pluralizes_words(): void
82 {
83 $this->assertSame('posts', $this->converter->pluralize('post'));
84 $this->assertSame('categories', $this->converter->pluralize('category'));
85 $this->assertSame('boxes', $this->converter->pluralize('box'));
86 $this->assertSame('churches', $this->converter->pluralize('church'));
87 $this->assertSame('bushes', $this->converter->pluralize('bush'));
88 $this->assertSame('postses', $this->converter->pluralize('posts')); // 'posts' ends with 's', gets 'es'
89 }
90
91 public function test_it_singularizes_words(): void
92 {
93 $this->assertSame('post', $this->converter->singularize('posts'));
94 $this->assertSame('category', $this->converter->singularize('categories'));
95 $this->assertSame('box', $this->converter->singularize('boxes'));
96 $this->assertSame('church', $this->converter->singularize('churches'));
97 $this->assertSame('bush', $this->converter->singularize('bushes'));
98 $this->assertSame('post', $this->converter->singularize('post')); // Already singular
99 }
100
101 public function test_it_handles_complex_nsid(): void
102 {
103 $className = $this->converter->nsidToClassName('com.atproto.repo.getRecord');
104
105 $this->assertSame('App\\Lexicon\\Com\\Atproto\\Repo\\GetRecord', $className);
106 }
107
108 public function test_it_gets_base_namespace(): void
109 {
110 $namespace = $this->converter->getBaseNamespace();
111
112 $this->assertSame('App\\Lexicon', $namespace);
113 }
114
115 public function test_it_sets_base_namespace(): void
116 {
117 $this->converter->setBaseNamespace('Custom\\Namespace');
118
119 $this->assertSame('Custom\\Namespace', $this->converter->getBaseNamespace());
120 }
121
122 public function test_it_strips_trailing_slash_from_namespace(): void
123 {
124 $converter = new NamingConverter('App\\Lexicon\\');
125
126 $this->assertSame('App\\Lexicon', $converter->getBaseNamespace());
127 }
128
129 public function test_it_handles_three_part_authority(): void
130 {
131 $className = $this->converter->nsidToClassName('com.example.api.getUser');
132
133 $this->assertSame('App\\Lexicon\\Com\\Example\\Api\\GetUser', $className);
134 }
135
136 public function test_it_handles_hyphens_in_names(): void
137 {
138 $className = $this->converter->toClassName('my-post');
139
140 $this->assertSame('MyPost', $className);
141 }
142
143 public function test_it_handles_underscores_in_names(): void
144 {
145 $className = $this->converter->toClassName('my_post');
146
147 $this->assertSame('MyPost', $className);
148 }
149
150 public function test_namespace_parts_are_reversed(): void
151 {
152 // app.bsky.feed should become App\Bsky\Feed (authority-first)
153 $namespace = $this->converter->nsidToNamespace('app.bsky.feed.post');
154
155 $this->assertStringContainsString('App\\Bsky\\Feed', $namespace);
156 }
157
158 public function test_it_handles_single_letter_parts(): void
159 {
160 $pascalCase = $this->converter->toPascalCase('a');
161
162 $this->assertSame('A', $pascalCase);
163 }
164
165 public function test_it_handles_numbers_in_names(): void
166 {
167 $className = $this->converter->toClassName('post2');
168
169 $this->assertSame('Post2', $className);
170 }
171}