aboutsummaryrefslogtreecommitdiff
path: root/tests/Jellyfin.Server.Implementations.Tests/Users/DisplayPreferencesManagerFailedWriteTests.cs
blob: 13688c75743a6877d8ab7e3d7201b78878db2dc1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
using System;
using System.Collections.Generic;
using System.Data.Common;
using Jellyfin.Server.Implementations.Tests.Item;
using Jellyfin.Server.Implementations.Users;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Xunit;

namespace Jellyfin.Server.Implementations.Tests.Users;

public sealed class DisplayPreferencesManagerFailedWriteTests : SqliteDbTestFixture
{
    private const string Client = "client";

    private static readonly Guid _userId = Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
    private static readonly Guid _itemId = Guid.Parse("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb");

    private readonly StatementInterceptor _statements;
    private readonly DisplayPreferencesManager _manager;

    public DisplayPreferencesManagerFailedWriteTests()
        : this(new StatementInterceptor())
    {
    }

    private DisplayPreferencesManagerFailedWriteTests(StatementInterceptor statements)
        : base(statements)
    {
        _statements = statements;
        _manager = new DisplayPreferencesManager(CreateDbContextFactory());
    }

    [Fact]
    public void SetCustomItemDisplayPreferences_InsertFails_KeepsThePreviousPreferences()
    {
        _manager.SetCustomItemDisplayPreferences(_userId, _itemId, Client, new Dictionary<string, string?> { ["first"] = "1" });

        _statements.FailInserts = true;
        Assert.Throws<DbUpdateException>(() => _manager.SetCustomItemDisplayPreferences(_userId, _itemId, Client, new Dictionary<string, string?> { ["second"] = "2" }));

        Assert.Equal(
            new Dictionary<string, string?> { ["first"] = "1" },
            _manager.ListCustomItemDisplayPreferences(_userId, _itemId, Client));
    }

    [Fact]
    public void SetCustomItemDisplayPreferences_AnotherRequestStoredAKeyAfterTheDelete_StoresThePreferencesItWasGiven()
    {
        _manager.SetCustomItemDisplayPreferences(_userId, _itemId, Client, new Dictionary<string, string?> { ["first"] = "1" });

        // The delete misses the row, as it does when another request stores it after this one's delete, so the unique
        // index rejects this request's insert.
        _statements.SkipNextDelete = true;
        _manager.SetCustomItemDisplayPreferences(_userId, _itemId, Client, new Dictionary<string, string?> { ["first"] = "2" });

        Assert.Equal(
            new Dictionary<string, string?> { ["first"] = "2" },
            _manager.ListCustomItemDisplayPreferences(_userId, _itemId, Client));
    }

    /// <summary>
    /// Fails inserts into CustomItemDisplayPreferences with the error SQLite reports when the unique index rejects a row, or
    /// leaves out the next delete from it.
    /// </summary>
    private sealed class StatementInterceptor : DbCommandInterceptor
    {
        public bool FailInserts { get; set; }

        public bool SkipNextDelete { get; set; }

        public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
        {
            FailInsert(command);
            return result;
        }

        public override InterceptionResult<int> NonQueryExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<int> result)
        {
            if (SkipNextDelete && command.CommandText.StartsWith("DELETE FROM \"CustomItemDisplayPreferences\"", StringComparison.Ordinal))
            {
                SkipNextDelete = false;
                return InterceptionResult<int>.SuppressWithResult(0);
            }

            FailInsert(command);
            return result;
        }

        private void FailInsert(DbCommand command)
        {
            if (FailInserts && command.CommandText.StartsWith("INSERT INTO \"CustomItemDisplayPreferences\"", StringComparison.Ordinal))
            {
                throw new SqliteException("SQLite Error 19: 'UNIQUE constraint failed: CustomItemDisplayPreferences.UserId, CustomItemDisplayPreferences.ItemId, CustomItemDisplayPreferences.Client, CustomItemDisplayPreferences.Key'.", 19);
            }
        }
    }
}