1+ // BSLLS:MagicNumber-off
2+ // BSLLS:LatinAndCyrillicSymbolInWord-off
3+ // BSLLS:DuplicateStringLiteral-off
4+ // BSLLS:LineLength-off
5+
6+ #Использовать ".."
7+ #Использовать "utils"
8+
9+ Перем МенеджерСущностей ;
10+
11+ Процедура ПередЗапускомТеста () Экспорт
12+
13+ ЗапускатьТестыPostgres = ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("TESTRUNNER_RUN_POSTGRES_TESTS" , "true" );
14+ ЗапускатьТестыSQLite = ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("TESTRUNNER_RUN_SQLITE_TESTS" , "true" );
15+
16+ ВыполнятьСбросТаблиц = Ложь ;
17+ Если ЗапускатьТестыSQLite = "true" Тогда
18+ СтрокаСоединения = "FullUri=file::memory:?cache=shared" ;
19+ //СтрокаСоединения = "Data Source=test.db";
20+ ТипКоннектора = Тип ("КоннекторSQLite" );
21+ ИначеЕсли ЗапускатьТестыPostgres = "true" Тогда
22+ Хост = ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_HOST" , "localhost" );
23+ Порт = ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_PORT" , "5432" );
24+ Пользователь = ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_USERNAME" , "postgres" );
25+ Пароль = ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_PASSWORD" , "postgres" );
26+ ИмяБД = ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_DATABASE" , "postgres" );
27+ СтрокаСоединения = СтрШаблон (
28+ "Host=%1;Username=%2;Password=%3;Database=%4;port=%5;" ,
29+ Хост,
30+ Пользователь,
31+ Пароль,
32+ ИмяБД,
33+ Порт
34+ );
35+ ТипКоннектора = Тип ("КоннекторPostgreSQL" );
36+ ВыполнятьСбросТаблиц = Истина ;
37+ Иначе
38+ ВызватьИсключение "Нет доступного коннектора для тестирования менеджера сущностей" ;
39+ КонецЕсли ;
40+
41+ МенеджерСущностей = Новый МенеджерСущностей(ТипКоннектора, СтрокаСоединения);
42+
43+ Если ВыполнятьСбросТаблиц Тогда
44+ Коннектор = МенеджерСущностей.ПолучитьКоннектор();
45+ Коннектор.Открыть(СтрокаСоединения, Новый Массив);
46+ ТестовыеУтилиты.УдалитьТаблицыВБазеДанных(Коннектор);
47+ Коннектор.Закрыть();
48+ КонецЕсли ;
49+
50+ ПодключитьСценарий(
51+ ОбъединитьПути(
52+ ТекущийКаталог(),
53+ "tests" ,
54+ "fixtures" ,
55+ "Автор.os"
56+ ),
57+ "Автор"
58+ );
59+ ПодключитьСценарий(
60+ ОбъединитьПути(
61+ ТекущийКаталог(),
62+ "tests" ,
63+ "fixtures" ,
64+ "СущностьБезГенерируемогоИдентификатора.os"
65+ ),
66+ "СущностьБезГенерируемогоИдентификатора"
67+ );
68+ ПодключитьСценарий(
69+ ОбъединитьПути(
70+ ТекущийКаталог(),
71+ "tests" ,
72+ "fixtures" ,
73+ "СущностьСоВсемиТипамиКолонок.os"
74+ ),
75+ "СущностьСоВсемиТипамиКолонок"
76+ );
77+
78+ МенеджерСущностей.ДобавитьКлассВМодель(Тип ("СущностьБезГенерируемогоИдентификатора" ));
79+ МенеджерСущностей.ДобавитьКлассВМодель(Тип ("Автор" ));
80+ МенеджерСущностей.ДобавитьКлассВМодель(Тип ("СущностьСоВсемиТипамиКолонок" ));
81+
82+ МенеджерСущностей.Инициализировать();
83+
84+ КонецПроцедуры
85+
86+ Процедура ПослеЗапускаТеста () Экспорт
87+ МенеджерСущностей.Закрыть();
88+ МенеджерСущностей = Неопределено ;
89+ КонецПроцедуры
90+
91+ &Тест
92+ Процедура НезависимыеТранзакцииВРазныхКонтекстах () Экспорт
93+ // Проверяем, что транзакции в разных контекстах не влияют друг на друга
94+
95+ // Контекст 1 - имитирует основной поток
96+ КонтекстID1 = МенеджерСущностей.НачатьТранзакцию();
97+ Ожидаем.Что(КонтекстID1, "Контекст 1 должен быть создан" ).Не_().Равно(Неопределено );
98+
99+ // Сохраняем сущность в контексте 1
100+ Автор1 = Новый Автор;
101+ Автор1.Имя = "Первый" ;
102+ Автор1.ВтороеИмя = "Автор" ;
103+ МенеджерСущностей.Сохранить(Автор1, КонтекстID1);
104+
105+ // Контекст 2 - имитирует фоновое задание
106+ КонтекстID2 = МенеджерСущностей.НачатьТранзакцию();
107+ Ожидаем.Что(КонтекстID2, "Контекст 2 должен быть создан" ).Не_().Равно(Неопределено );
108+ Ожидаем.Что(КонтекстID2, "Контексты должны быть разными" ).Не_().Равно(КонтекстID1);
109+
110+ // Сохраняем другую сущность в контексте 2
111+ Автор2 = Новый Автор;
112+ Автор2.Имя = "Второй" ;
113+ Автор2.ВтороеИмя = "Автор" ;
114+ МенеджерСущностей.Сохранить(Автор2, КонтекстID2);
115+
116+ // Откатываем только контекст 2 (имитируем ошибку в фоновом задании)
117+ МенеджерСущностей.ОтменитьТранзакцию(КонтекстID2);
118+
119+ // Фиксируем контекст 1 (основной поток завершается успешно)
120+ МенеджерСущностей.ЗафиксироватьТранзакцию(КонтекстID1);
121+
122+ // Проверяем результат - должен остаться только автор из контекста 1
123+ Результат = МенеджерСущностей.ПолучитьКоннектор().ВыполнитьЗапрос("SELECT * FROM Авторы" );
124+ Ожидаем.Что(Результат, "Должен остаться только один автор" ).ИмеетДлину(1 );
125+ Ожидаем.Что(Результат[0 ].Имя, "Должен остаться автор из первого контекста" ).Равно("Первый" );
126+ КонецПроцедуры
127+
128+ &Тест
129+ Процедура МногократныеНезависимыеТранзакции () Экспорт
130+ // Проверяем работу нескольких независимых транзакций подряд
131+
132+ МассивКонтекстов = Новый Массив;
133+
134+ // Создаем несколько контекстов (имитируем множественные фоновые задания)
135+ Для Индекс = 1 По 3 Цикл
136+ КонтекстID = МенеджерСущностей.НачатьТранзакцию();
137+ МассивКонтекстов.Добавить(КонтекстID);
138+
139+ // В каждом контексте сохраняем автора
140+ Автор = Новый Автор;
141+ Автор.Имя = "Автор" + Индекс;
142+ Автор.ВтороеИмя = "Контекст" + Индекс;
143+ МенеджерСущностей.Сохранить(Автор, КонтекстID);
144+ КонецЦикла ;
145+
146+ // Фиксируем все транзакции
147+ Для Каждого КонтекстID Из МассивКонтекстов Цикл
148+ МенеджерСущностей.ЗафиксироватьТранзакцию(КонтекстID);
149+ КонецЦикла ;
150+
151+ // Проверяем, что все авторы сохранились
152+ Результат = МенеджерСущностей.ПолучитьКоннектор().ВыполнитьЗапрос("SELECT * FROM Авторы ORDER BY Имя" );
153+ Ожидаем.Что(Результат, "Должны сохраниться все три автора" ).ИмеетДлину(3 );
154+
155+ Для Индекс = 0 По 2 Цикл
156+ ОжидаемоеИмя = "Автор" + (Индекс + 1 );
157+ Ожидаем.Что(Результат[Индекс].Имя, "Автор должен быть сохранен корректно" ).Равно(ОжидаемоеИмя);
158+ КонецЦикла ;
159+ КонецПроцедуры
160+
161+ &Тест
162+ Процедура ОбратнаяСовместимостьБезКонтекстов () Экспорт
163+ // Проверяем, что старый API без указания контекстов продолжает работать
164+
165+ МенеджерСущностей.НачатьТранзакцию(); // Без возврата КонтекстID
166+
167+ Автор = Новый Автор;
168+ Автор.Имя = "Старый" ;
169+ Автор.ВтороеИмя = "API" ;
170+ МенеджерСущностей.Сохранить(Автор); // Без указания контекста
171+
172+ МенеджерСущностей.ЗафиксироватьТранзакцию(); // Без указания контекста
173+
174+ // Проверяем результат
175+ Результат = МенеджерСущностей.ПолучитьКоннектор().ВыполнитьЗапрос("SELECT * FROM Авторы" );
176+ Ожидаем.Что(Результат, "Автор должен быть сохранен" ).ИмеетДлину(1 );
177+ Ожидаем.Что(Результат[0 ].Имя, "Имя автора должно быть корректным" ).Равно("Старый" );
178+ КонецПроцедуры
179+
180+ &Тест
181+ Процедура СмешанноеИспользованиеКонтекстовИБезКонтекстов () Экспорт
182+ // Проверяем совместимость нового и старого API
183+
184+ // Новый API с контекстом
185+ КонтекстID = МенеджерСущностей.НачатьТранзакцию();
186+
187+ Автор1 = Новый Автор;
188+ Автор1.Имя = "Новый" ;
189+ Автор1.ВтороеИмя = "API" ;
190+ МенеджерСущностей.Сохранить(Автор1, КонтекстID);
191+
192+ // Старый API без контекста (параллельно)
193+ МенеджерСущностей.НачатьТранзакцию();
194+
195+ Автор2 = Новый Автор;
196+ Автор2.Имя = "Старый" ;
197+ Автор2.ВтороеИмя = "API" ;
198+ МенеджерСущностей.Сохранить(Автор2);
199+
200+ // Фиксируем обе транзакции
201+ МенеджерСущностей.ЗафиксироватьТранзакцию(КонтекстID);
202+ МенеджерСущностей.ЗафиксироватьТранзакцию();
203+
204+ // Проверяем результат
205+ Результат = МенеджерСущностей.ПолучитьКоннектор().ВыполнитьЗапрос("SELECT * FROM Авторы ORDER BY Имя" );
206+ Ожидаем.Что(Результат, "Должны быть сохранены оба автора" ).ИмеетДлину(2 );
207+ Ожидаем.Что(Результат[0 ].Имя, "Первый автор" ).Равно("Новый" );
208+ Ожидаем.Что(Результат[1 ].Имя, "Второй автор" ).Равно("Старый" );
209+ КонецПроцедуры
210+
211+ &Тест
212+ Процедура СимуляцияФоновогоЗаданияСОшибкой () Экспорт
213+ // Имитируем ситуацию, когда фоновое задание завершается с ошибкой
214+
215+ // Основной поток начинает транзакцию
216+ ОсновнойКонтекстID = МенеджерСущностей.НачатьТранзакцию();
217+
218+ Автор1 = Новый Автор;
219+ Автор1.Имя = "Основной" ;
220+ Автор1.ВтороеИмя = "Поток" ;
221+ МенеджерСущностей.Сохранить(Автор1, ОсновнойКонтекстID);
222+
223+ // Фоновое задание начинает свою транзакцию
224+ ФоновыйКонтекстID = МенеджерСущностей.НачатьТранзакцию();
225+
226+ Автор2 = Новый Автор;
227+ Автор2.Имя = "Фоновое" ;
228+ Автор2.ВтороеИмя = "Задание" ;
229+ МенеджерСущностей.Сохранить(Автор2, ФоновыйКонтекстID);
230+
231+ // Основной поток завершается успешно
232+ МенеджерСущностей.ЗафиксироватьТранзакцию(ОсновнойКонтекстID);
233+
234+ // Фоновое задание завершается с ошибкой (откат)
235+ МенеджерСущностей.ОтменитьТранзакцию(ФоновыйКонтекстID);
236+
237+ // Проверяем, что сохранился только автор из основного потока
238+ Результат = МенеджерСущностей.ПолучитьКоннектор().ВыполнитьЗапрос("SELECT * FROM Авторы" );
239+ Ожидаем.Что(Результат, "Должен остаться только автор из основного потока" ).ИмеетДлину(1 );
240+ Ожидаем.Что(Результат[0 ].Имя, "Должен остаться правильный автор" ).Равно("Основной" );
241+ КонецПроцедуры
242+
243+ // Тест для демонстрации реальной работы с ФоновыеЗадания API
244+ // (будет работать только при наличии поддержки фоновых заданий)
245+ &Тест
246+ Процедура ДемонстрацияФоновыхЗаданий () Экспорт
247+ // Проверяем доступность API фоновых заданий
248+
249+ ДоступныФоновыеЗадания = Ложь ;
250+ Попытка
251+ Если ТипЗнч (ФоновыеЗадания ) = Тип ("МенеджерФоновыхЗаданий" ) Тогда
252+ ДоступныФоновыеЗадания = Истина ;
253+ КонецЕсли ;
254+ Исключение
255+ // API фоновых заданий недоступен
256+ КонецПопытки ;
257+
258+ Если НЕ ДоступныФоновыеЗадания Тогда
259+ Сообщить ("API фоновых заданий недоступен. Пропускаем тест реальных фоновых заданий." );
260+ Возврат ;
261+ КонецЕсли ;
262+
263+ // Начинаем транзакцию в основном потоке
264+ ОсновнойКонтекстID = МенеджерСущностей.НачатьТранзакцию();
265+
266+ Автор1 = Новый Автор;
267+ Автор1.Имя = "Основной" ;
268+ Автор1.ВтороеИмя = "Поток" ;
269+ МенеджерСущностей.Сохранить(Автор1, ОсновнойКонтекстID);
270+
271+ // Запускаем реальное фоновое задание
272+ ПараметрыЗадания = Новый Структура;
273+ ПараметрыЗадания.Вставить("МенеджерСущностей" , МенеджерСущностей);
274+
275+ Попытка
276+ ФоновоеЗадание = ФоновыеЗадания .Выполнить("ФоновоеЗаданиеСозданияАвтора" , ПараметрыЗадания);
277+
278+ // Ждем завершения фонового задания (максимум 5 секунд)
279+ СчетчикОжидания = 0 ;
280+ Пока ФоновоеЗадание.Состояние = СостояниеФоновогоЗадания.Активно И СчетчикОжидания < 50 Цикл
281+ Приостановить(100 ); // 100 мс
282+ СчетчикОжидания = СчетчикОжидания + 1 ;
283+ КонецЦикла ;
284+
285+ // Фиксируем транзакцию основного потока
286+ МенеджерСущностей.ЗафиксироватьТранзакцию(ОсновнойКонтекстID);
287+
288+ // Проверяем результат
289+ Результат = МенеджерСущностей.ПолучитьКоннектор().ВыполнитьЗапрос("SELECT * FROM Авторы ORDER BY Имя" );
290+
291+ Если ФоновоеЗадание.Состояние = СостояниеФоновогоЗадания.Завершено Тогда
292+ Ожидаем.Что(Результат, "Должны быть сохранены оба автора" ).ИмеетДлину(2 );
293+ Ожидаем.Что(Результат[0 ].Имя, "Первый автор" ).Равно("Основной" );
294+ Ожидаем.Что(Результат[1 ].Имя, "Второй автор" ).Равно("Фоновое" );
295+ Иначе
296+ Сообщить ("Фоновое задание не завершилось в ожидаемое время. Состояние: " + ФоновоеЗадание.Состояние);
297+ Ожидаем.Что(Результат, "Должен остаться только автор из основного потока" ).ИмеетДлину(1 );
298+ КонецЕсли ;
299+
300+ Исключение
301+ Сообщить ("Ошибка при запуске фонового задания: " + ОписаниеОшибки ());
302+ // Все равно фиксируем основной поток
303+ МенеджерСущностей.ЗафиксироватьТранзакцию(ОсновнойКонтекстID);
304+ КонецПопытки ;
305+ КонецПроцедуры
306+
307+ // Процедура для выполнения в фоновом задании
308+ Процедура ФоновоеЗаданиеСозданияАвтора (ПараметрыЗадания ) Экспорт
309+ МенеджерСущностей = ПараметрыЗадания.МенеджерСущностей;
310+
311+ // Начинаем транзакцию в фоновом задании
312+ КонтекстID = МенеджерСущностей.НачатьТранзакцию();
313+
314+ Попытка
315+ Автор = Новый Автор;
316+ Автор.Имя = "Фоновое" ;
317+ Автор.ВтороеИмя = "Задание" ;
318+ МенеджерСущностей.Сохранить(Автор, КонтекстID);
319+
320+ // Фиксируем транзакцию
321+ МенеджерСущностей.ЗафиксироватьТранзакцию(КонтекстID);
322+ Исключение
323+ // В случае ошибки откатываем транзакцию
324+ МенеджерСущностей.ОтменитьТранзакцию(КонтекстID);
325+ ВызватьИсключение ;
326+ КонецПопытки ;
327+ КонецПроцедуры
0 commit comments